mirror of
https://github.com/jambonz/freeswitch-modules.git
synced 2026-01-25 02:08:27 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81d340ad2f | |||
| 9857c0cf33 | |||
| 10fa73e42f | |||
| d7291706ba |
@@ -102,12 +102,11 @@ namespace {
|
||||
return path;
|
||||
}
|
||||
|
||||
static void eventCallback(const char* sessionId, const char* bugname,
|
||||
assemblyai::AudioPipe::NotifyEvent_t event, const char* message, bool finished) {
|
||||
static void eventCallback(const char* sessionId, assemblyai::AudioPipe::NotifyEvent_t event, const char* message, bool finished) {
|
||||
switch_core_session_t* session = switch_core_session_locate(sessionId);
|
||||
if (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, bugname);
|
||||
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
|
||||
if (bug) {
|
||||
private_t* tech_pvt = (private_t*) switch_core_media_bug_get_user_data(bug);
|
||||
if (tech_pvt) {
|
||||
@@ -195,8 +194,7 @@ namespace {
|
||||
tech_pvt->channels = channels;
|
||||
tech_pvt->id = ++idxCallCount;
|
||||
tech_pvt->buffer_overrun_notified = 0;
|
||||
strncpy(tech_pvt->bugname, bugname, MAX_BUG_LEN);
|
||||
|
||||
|
||||
size_t buflen = LWS_PRE + (FRAME_SIZE_8000 * desiredSampling / 8000 * channels * 1000 / RTP_PACKETIZATION_PERIOD * nAudioBufferSecs);
|
||||
|
||||
const char* apiKey = switch_channel_get_variable(channel, "ASSEMBLYAI_API_KEY");
|
||||
@@ -206,7 +204,7 @@ namespace {
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
assemblyai::AudioPipe* ap = new assemblyai::AudioPipe(tech_pvt->sessionId, bugname, tech_pvt->host, tech_pvt->port, tech_pvt->path,
|
||||
assemblyai::AudioPipe* ap = new assemblyai::AudioPipe(tech_pvt->sessionId, tech_pvt->host, tech_pvt->port, tech_pvt->path,
|
||||
buflen, read_impl.decoded_bytes_per_packet, apiKey, eventCallback);
|
||||
if (!ap) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error allocating AudioPipe\n");
|
||||
@@ -253,8 +251,7 @@ extern "C" {
|
||||
switch_status_t aai_transcribe_init() {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_assemblyai_transcribe: audio buffer (in secs): %d secs\n", nAudioBufferSecs);
|
||||
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE ;
|
||||
//| LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE || LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
|
||||
assemblyai::AudioPipe::initialize(logs, lws_logger);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "AudioPipe::initialize completed\n");
|
||||
@@ -309,7 +306,7 @@ extern "C" {
|
||||
|
||||
switch_status_t aai_transcribe_session_stop(switch_core_session_t *session,int channelIsClosing, char* bugname) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, bugname);
|
||||
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
|
||||
if (!bug) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "aai_transcribe_session_stop: no bug - websocket conection already closed\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
|
||||
@@ -72,7 +72,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR: %s, response status %d\n", in ? (char *)in : "(null)", rc);
|
||||
if (ap) {
|
||||
ap->m_state = LWS_CLIENT_FAILED;
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECT_FAIL, (char *) in, ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECT_FAIL, (char *) in, ap->isFinished());
|
||||
}
|
||||
else {
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR unable to find wsi %p..\n", wsi);
|
||||
@@ -87,7 +87,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
*ppAp = ap;
|
||||
ap->m_vhd = vhd;
|
||||
ap->m_state = LWS_CLIENT_CONNECTED;
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECT_SUCCESS, NULL, ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECT_SUCCESS, NULL, ap->isFinished());
|
||||
}
|
||||
else {
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_ESTABLISHED %s unable to find wsi %p..\n", ap->m_uuid.c_str(), wsi);
|
||||
@@ -105,12 +105,12 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
// closed by us
|
||||
|
||||
lwsl_debug("%s socket closed by us\n", ap->m_uuid.c_str());
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECTION_CLOSED_GRACEFULLY, NULL, ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECTION_CLOSED_GRACEFULLY, NULL, ap->isFinished());
|
||||
}
|
||||
else if (ap->m_state == LWS_CLIENT_CONNECTED) {
|
||||
// closed by far end
|
||||
lwsl_info("%s socket closed by far end\n", ap->m_uuid.c_str());
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECTION_DROPPED, NULL, ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECTION_DROPPED, NULL, ap->isFinished());
|
||||
}
|
||||
ap->m_state = LWS_CLIENT_DISCONNECTED;
|
||||
ap->setClosed();
|
||||
@@ -172,7 +172,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
if (lws_is_final_fragment(wsi)) {
|
||||
if (nullptr != ap->m_recv_buf) {
|
||||
std::string msg((char *)ap->m_recv_buf, ap->m_recv_buf_ptr - ap->m_recv_buf);
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::MESSAGE, msg.c_str(), ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::MESSAGE, msg.c_str(), ap->isFinished());
|
||||
if (nullptr != ap->m_recv_buf) free(ap->m_recv_buf);
|
||||
}
|
||||
ap->m_recv_buf = ap->m_recv_buf_ptr = nullptr;
|
||||
@@ -261,7 +261,6 @@ static const lws_retry_bo_t retry = {
|
||||
};
|
||||
|
||||
struct lws_context *AudioPipe::context = nullptr;
|
||||
std::thread AudioPipe::serviceThread;
|
||||
std::string AudioPipe::protocolName;
|
||||
std::mutex AudioPipe::mutex_connects;
|
||||
std::mutex AudioPipe::mutex_disconnects;
|
||||
@@ -430,28 +429,26 @@ bool AudioPipe::lws_service_thread() {
|
||||
|
||||
void AudioPipe::initialize(int loglevel, log_emit_function logger) {
|
||||
|
||||
//lws_set_log_level(loglevel, logger);
|
||||
lws_set_log_level(loglevel, logger);
|
||||
|
||||
lwsl_notice("AudioPipe::initialize starting\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
std::thread t(&AudioPipe::lws_service_thread);
|
||||
stopFlag = false;
|
||||
serviceThread = std::thread(&AudioPipe::lws_service_thread);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
bool AudioPipe::deinitialize() {
|
||||
lwsl_notice("AudioPipe::deinitialize\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
stopFlag = true;
|
||||
if (serviceThread.joinable()) {
|
||||
serviceThread.join();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// instance members
|
||||
AudioPipe::AudioPipe(const char* uuid, const char* bugname, const char* host, unsigned int port, const char* path,
|
||||
AudioPipe::AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path,
|
||||
size_t bufLen, size_t minFreespace, const char* apiKey, notifyHandler_t callback) :
|
||||
m_uuid(uuid), m_host(host), m_port(port), m_path(path), m_finished(false), m_bugname(bugname),
|
||||
m_uuid(uuid), m_host(host), m_port(port), m_path(path), m_finished(false),
|
||||
m_audio_buffer_min_freespace(minFreespace), m_audio_buffer_max_len(bufLen), m_gracefulShutdown(false),
|
||||
m_audio_buffer_write_offset(LWS_PRE), m_recv_buf(nullptr), m_recv_buf_ptr(nullptr),
|
||||
m_state(LWS_CLIENT_IDLE), m_wsi(nullptr), m_vhd(nullptr), m_apiKey(apiKey), m_callback(callback) {
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
MESSAGE
|
||||
};
|
||||
typedef void (*log_emit_function)(int level, const char *line);
|
||||
typedef void (*notifyHandler_t)(const char *sessionId,const char* bugname, NotifyEvent_t event, const char* message, bool finished);
|
||||
typedef void (*notifyHandler_t)(const char *sessionId, NotifyEvent_t event, const char* message, bool finished);
|
||||
|
||||
struct lws_per_vhost_data {
|
||||
struct lws_context *context;
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
static bool lws_service_thread();
|
||||
|
||||
// constructor
|
||||
AudioPipe(const char* uuid, const char* bugname, const char* host, unsigned int port, const char* path,
|
||||
AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path,
|
||||
size_t bufLen, size_t minFreespace, const char* apiKey, notifyHandler_t callback);
|
||||
~AudioPipe();
|
||||
|
||||
@@ -86,7 +86,6 @@ public:
|
||||
void operator=(const AudioPipe&) = delete;
|
||||
|
||||
private:
|
||||
static std::thread serviceThread;
|
||||
|
||||
static int lws_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
static struct lws_context *context;
|
||||
|
||||
@@ -74,7 +74,7 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
||||
void *pUserData;
|
||||
uint32_t samples_per_second;
|
||||
|
||||
if (switch_channel_get_private(channel, bugname)) {
|
||||
if (switch_channel_get_private(channel, MY_BUG_NAME)) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing bug from previous transcribe\n");
|
||||
do_stop(session, bugname);
|
||||
}
|
||||
@@ -94,7 +94,7 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
||||
if ((status = switch_core_media_bug_add(session, "aai_transcribe", NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
switch_channel_set_private(channel, bugname, bug);
|
||||
switch_channel_set_private(channel, MY_BUG_NAME, bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "added media bug for assemblyai transcribe\n");
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
@@ -105,7 +105,7 @@ static switch_status_t do_stop(switch_core_session_t *session, char* bugname)
|
||||
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, bugname);
|
||||
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 transcribe.\n");
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#define MAX_RECV_BUF_SIZE (65 * 1024 * 10)
|
||||
#define RECV_BUF_REALLOC_SIZE (8 * 1024)
|
||||
|
||||
using namespace drachtio;
|
||||
|
||||
namespace {
|
||||
static const char* basicAuthUser = std::getenv("MOD_AUDIO_FORK_HTTP_AUTH_USER");
|
||||
@@ -268,7 +267,6 @@ static const lws_retry_bo_t retry = {
|
||||
};
|
||||
|
||||
struct lws_context *AudioPipe::context = nullptr;
|
||||
std::thread AudioPipe::serviceThread;
|
||||
std::string AudioPipe::protocolName;
|
||||
std::mutex AudioPipe::mutex_connects;
|
||||
std::mutex AudioPipe::mutex_disconnects;
|
||||
@@ -438,21 +436,19 @@ bool AudioPipe::lws_service_thread() {
|
||||
|
||||
void AudioPipe::initialize(const char* protocol, int loglevel, log_emit_function logger) {
|
||||
protocolName = protocol;
|
||||
//lws_set_log_level(loglevel, logger);
|
||||
lws_set_log_level(loglevel, logger);
|
||||
|
||||
lwsl_notice("AudioPipe::initialize starting\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
std::thread t(&AudioPipe::lws_service_thread);
|
||||
stopFlag = false;
|
||||
serviceThread = std::thread(&AudioPipe::lws_service_thread);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
bool AudioPipe::deinitialize() {
|
||||
lwsl_notice("AudioPipe::deinitialize\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
stopFlag = true;
|
||||
if (serviceThread.joinable()) {
|
||||
serviceThread.join();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+127
-132
@@ -10,140 +10,135 @@
|
||||
|
||||
#include <libwebsockets.h>
|
||||
|
||||
namespace drachtio {
|
||||
class AudioPipe {
|
||||
public:
|
||||
enum LwsState_t {
|
||||
LWS_CLIENT_IDLE,
|
||||
LWS_CLIENT_CONNECTING,
|
||||
LWS_CLIENT_CONNECTED,
|
||||
LWS_CLIENT_FAILED,
|
||||
LWS_CLIENT_DISCONNECTING,
|
||||
LWS_CLIENT_DISCONNECTED
|
||||
};
|
||||
enum NotifyEvent_t {
|
||||
CONNECT_SUCCESS,
|
||||
CONNECT_FAIL,
|
||||
CONNECTION_DROPPED,
|
||||
CONNECTION_CLOSED_GRACEFULLY,
|
||||
MESSAGE
|
||||
};
|
||||
typedef void (*log_emit_function)(int level, const char *line);
|
||||
typedef void (*notifyHandler_t)(const char *sessionId, const char* bugname, NotifyEvent_t event, const char* message);
|
||||
|
||||
class AudioPipe {
|
||||
public:
|
||||
enum LwsState_t {
|
||||
LWS_CLIENT_IDLE,
|
||||
LWS_CLIENT_CONNECTING,
|
||||
LWS_CLIENT_CONNECTED,
|
||||
LWS_CLIENT_FAILED,
|
||||
LWS_CLIENT_DISCONNECTING,
|
||||
LWS_CLIENT_DISCONNECTED
|
||||
};
|
||||
enum NotifyEvent_t {
|
||||
CONNECT_SUCCESS,
|
||||
CONNECT_FAIL,
|
||||
CONNECTION_DROPPED,
|
||||
CONNECTION_CLOSED_GRACEFULLY,
|
||||
MESSAGE
|
||||
};
|
||||
typedef void (*log_emit_function)(int level, const char *line);
|
||||
typedef void (*notifyHandler_t)(const char *sessionId, const char* bugname, NotifyEvent_t event, const char* message);
|
||||
|
||||
struct lws_per_vhost_data {
|
||||
struct lws_context *context;
|
||||
struct lws_vhost *vhost;
|
||||
const struct lws_protocols *protocol;
|
||||
};
|
||||
|
||||
static void initialize(const char* protocolName, int loglevel, log_emit_function logger);
|
||||
static bool deinitialize();
|
||||
static bool lws_service_thread();
|
||||
|
||||
// constructor
|
||||
AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path, int sslFlags,
|
||||
size_t bufLen, size_t minFreespace, const char* username, const char* password, char* bugname, notifyHandler_t callback);
|
||||
~AudioPipe();
|
||||
|
||||
LwsState_t getLwsState(void) { return m_state; }
|
||||
void connect(void);
|
||||
void bufferForSending(const char* text);
|
||||
size_t binarySpaceAvailable(void) {
|
||||
return m_audio_buffer_max_len - m_audio_buffer_write_offset;
|
||||
}
|
||||
size_t binaryMinSpace(void) {
|
||||
return m_audio_buffer_min_freespace;
|
||||
}
|
||||
char * binaryWritePtr(void) {
|
||||
return (char *) m_audio_buffer + m_audio_buffer_write_offset;
|
||||
}
|
||||
void binaryWritePtrAdd(size_t len) {
|
||||
m_audio_buffer_write_offset += len;
|
||||
}
|
||||
void binaryWritePtrResetToZero(void) {
|
||||
m_audio_buffer_write_offset = 0;
|
||||
}
|
||||
void lockAudioBuffer(void) {
|
||||
m_audio_mutex.lock();
|
||||
}
|
||||
void unlockAudioBuffer(void) ;
|
||||
bool hasBasicAuth(void) {
|
||||
return !m_username.empty() && !m_password.empty();
|
||||
}
|
||||
|
||||
void getBasicAuth(std::string& username, std::string& password) {
|
||||
username = m_username;
|
||||
password = m_password;
|
||||
}
|
||||
|
||||
void do_graceful_shutdown();
|
||||
bool isGracefulShutdown(void) {
|
||||
return m_gracefulShutdown;
|
||||
}
|
||||
|
||||
void close() ;
|
||||
|
||||
// no default constructor or copying
|
||||
AudioPipe() = delete;
|
||||
AudioPipe(const AudioPipe&) = delete;
|
||||
void operator=(const AudioPipe&) = delete;
|
||||
|
||||
private:
|
||||
static std::thread serviceThread;
|
||||
|
||||
static int lws_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
static struct lws_context *context;
|
||||
static std::string protocolName;
|
||||
static std::mutex mutex_connects;
|
||||
static std::mutex mutex_disconnects;
|
||||
static std::mutex mutex_writes;
|
||||
static std::list<AudioPipe*> pendingConnects;
|
||||
static std::list<AudioPipe*> pendingDisconnects;
|
||||
static std::list<AudioPipe*> pendingWrites;
|
||||
static log_emit_function logger;
|
||||
|
||||
static std::mutex mapMutex;
|
||||
static bool stopFlag;
|
||||
|
||||
static AudioPipe* findAndRemovePendingConnect(struct lws *wsi);
|
||||
static AudioPipe* findPendingConnect(struct lws *wsi);
|
||||
static void addPendingConnect(AudioPipe* ap);
|
||||
static void addPendingDisconnect(AudioPipe* ap);
|
||||
static void addPendingWrite(AudioPipe* ap);
|
||||
static void processPendingConnects(lws_per_vhost_data *vhd);
|
||||
static void processPendingDisconnects(lws_per_vhost_data *vhd);
|
||||
static void processPendingWrites(void);
|
||||
|
||||
bool connect_client(struct lws_per_vhost_data *vhd);
|
||||
|
||||
LwsState_t m_state;
|
||||
std::string m_uuid;
|
||||
std::string m_host;
|
||||
std::string m_bugname;
|
||||
unsigned int m_port;
|
||||
std::string m_path;
|
||||
std::string m_metadata;
|
||||
std::mutex m_text_mutex;
|
||||
std::mutex m_audio_mutex;
|
||||
int m_sslFlags;
|
||||
struct lws *m_wsi;
|
||||
uint8_t *m_audio_buffer;
|
||||
size_t m_audio_buffer_max_len;
|
||||
size_t m_audio_buffer_write_offset;
|
||||
size_t m_audio_buffer_min_freespace;
|
||||
uint8_t* m_recv_buf;
|
||||
uint8_t* m_recv_buf_ptr;
|
||||
size_t m_recv_buf_len;
|
||||
struct lws_per_vhost_data* m_vhd;
|
||||
notifyHandler_t m_callback;
|
||||
log_emit_function m_logger;
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
bool m_gracefulShutdown;
|
||||
struct lws_per_vhost_data {
|
||||
struct lws_context *context;
|
||||
struct lws_vhost *vhost;
|
||||
const struct lws_protocols *protocol;
|
||||
};
|
||||
|
||||
} // namespace drachtio
|
||||
static void initialize(const char* protocolName, int loglevel, log_emit_function logger);
|
||||
static bool deinitialize();
|
||||
static bool lws_service_thread();
|
||||
|
||||
// constructor
|
||||
AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path, int sslFlags,
|
||||
size_t bufLen, size_t minFreespace, const char* username, const char* password, char* bugname, notifyHandler_t callback);
|
||||
~AudioPipe();
|
||||
|
||||
LwsState_t getLwsState(void) { return m_state; }
|
||||
void connect(void);
|
||||
void bufferForSending(const char* text);
|
||||
size_t binarySpaceAvailable(void) {
|
||||
return m_audio_buffer_max_len - m_audio_buffer_write_offset;
|
||||
}
|
||||
size_t binaryMinSpace(void) {
|
||||
return m_audio_buffer_min_freespace;
|
||||
}
|
||||
char * binaryWritePtr(void) {
|
||||
return (char *) m_audio_buffer + m_audio_buffer_write_offset;
|
||||
}
|
||||
void binaryWritePtrAdd(size_t len) {
|
||||
m_audio_buffer_write_offset += len;
|
||||
}
|
||||
void binaryWritePtrResetToZero(void) {
|
||||
m_audio_buffer_write_offset = 0;
|
||||
}
|
||||
void lockAudioBuffer(void) {
|
||||
m_audio_mutex.lock();
|
||||
}
|
||||
void unlockAudioBuffer(void) ;
|
||||
bool hasBasicAuth(void) {
|
||||
return !m_username.empty() && !m_password.empty();
|
||||
}
|
||||
|
||||
void getBasicAuth(std::string& username, std::string& password) {
|
||||
username = m_username;
|
||||
password = m_password;
|
||||
}
|
||||
|
||||
void do_graceful_shutdown();
|
||||
bool isGracefulShutdown(void) {
|
||||
return m_gracefulShutdown;
|
||||
}
|
||||
|
||||
void close() ;
|
||||
|
||||
// no default constructor or copying
|
||||
AudioPipe() = delete;
|
||||
AudioPipe(const AudioPipe&) = delete;
|
||||
void operator=(const AudioPipe&) = delete;
|
||||
|
||||
private:
|
||||
|
||||
static int lws_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
static struct lws_context *context;
|
||||
static std::string protocolName;
|
||||
static std::mutex mutex_connects;
|
||||
static std::mutex mutex_disconnects;
|
||||
static std::mutex mutex_writes;
|
||||
static std::list<AudioPipe*> pendingConnects;
|
||||
static std::list<AudioPipe*> pendingDisconnects;
|
||||
static std::list<AudioPipe*> pendingWrites;
|
||||
static log_emit_function logger;
|
||||
|
||||
static std::mutex mapMutex;
|
||||
static bool stopFlag;
|
||||
|
||||
static AudioPipe* findAndRemovePendingConnect(struct lws *wsi);
|
||||
static AudioPipe* findPendingConnect(struct lws *wsi);
|
||||
static void addPendingConnect(AudioPipe* ap);
|
||||
static void addPendingDisconnect(AudioPipe* ap);
|
||||
static void addPendingWrite(AudioPipe* ap);
|
||||
static void processPendingConnects(lws_per_vhost_data *vhd);
|
||||
static void processPendingDisconnects(lws_per_vhost_data *vhd);
|
||||
static void processPendingWrites(void);
|
||||
|
||||
bool connect_client(struct lws_per_vhost_data *vhd);
|
||||
|
||||
LwsState_t m_state;
|
||||
std::string m_uuid;
|
||||
std::string m_host;
|
||||
std::string m_bugname;
|
||||
unsigned int m_port;
|
||||
std::string m_path;
|
||||
std::string m_metadata;
|
||||
std::mutex m_text_mutex;
|
||||
std::mutex m_audio_mutex;
|
||||
int m_sslFlags;
|
||||
struct lws *m_wsi;
|
||||
uint8_t *m_audio_buffer;
|
||||
size_t m_audio_buffer_max_len;
|
||||
size_t m_audio_buffer_write_offset;
|
||||
size_t m_audio_buffer_min_freespace;
|
||||
uint8_t* m_recv_buf;
|
||||
uint8_t* m_recv_buf_ptr;
|
||||
size_t m_recv_buf_len;
|
||||
struct lws_per_vhost_data* m_vhd;
|
||||
notifyHandler_t m_callback;
|
||||
log_emit_function m_logger;
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
bool m_gracefulShutdown;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+17
-21
@@ -156,7 +156,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
static void eventCallback(const char* sessionId, const char* bugname, drachtio::AudioPipe::NotifyEvent_t event, const char* message) {
|
||||
static void eventCallback(const char* sessionId, const char* bugname, AudioPipe::NotifyEvent_t event, const char* message) {
|
||||
switch_core_session_t* session = switch_core_session_locate(sessionId);
|
||||
if (session) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
@@ -165,16 +165,16 @@ namespace {
|
||||
private_t* tech_pvt = (private_t*) switch_core_media_bug_get_user_data(bug);
|
||||
if (tech_pvt) {
|
||||
switch (event) {
|
||||
case drachtio::AudioPipe::CONNECT_SUCCESS:
|
||||
case AudioPipe::CONNECT_SUCCESS:
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "connection successful\n");
|
||||
tech_pvt->responseHandler(session, EVENT_CONNECT_SUCCESS, NULL);
|
||||
if (strlen(tech_pvt->initialMetadata) > 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "sending initial metadata %s\n", tech_pvt->initialMetadata);
|
||||
drachtio::AudioPipe *pAudioPipe = static_cast<drachtio::AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
AudioPipe *pAudioPipe = static_cast<AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
pAudioPipe->bufferForSending(tech_pvt->initialMetadata);
|
||||
}
|
||||
break;
|
||||
case drachtio::AudioPipe::CONNECT_FAIL:
|
||||
case AudioPipe::CONNECT_FAIL:
|
||||
{
|
||||
// first thing: we can no longer access the AudioPipe
|
||||
std::stringstream json;
|
||||
@@ -184,18 +184,18 @@ namespace {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "connection failed: %s\n", message);
|
||||
}
|
||||
break;
|
||||
case drachtio::AudioPipe::CONNECTION_DROPPED:
|
||||
case AudioPipe::CONNECTION_DROPPED:
|
||||
// first thing: we can no longer access the AudioPipe
|
||||
tech_pvt->pAudioPipe = nullptr;
|
||||
tech_pvt->responseHandler(session, EVENT_DISCONNECT, NULL);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "connection dropped from far end\n");
|
||||
break;
|
||||
case drachtio::AudioPipe::CONNECTION_CLOSED_GRACEFULLY:
|
||||
case AudioPipe::CONNECTION_CLOSED_GRACEFULLY:
|
||||
// first thing: we can no longer access the AudioPipe
|
||||
tech_pvt->pAudioPipe = nullptr;
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connection closed gracefully\n");
|
||||
break;
|
||||
case drachtio::AudioPipe::MESSAGE:
|
||||
case AudioPipe::MESSAGE:
|
||||
processIncomingMessage(tech_pvt, session, message);
|
||||
break;
|
||||
}
|
||||
@@ -239,7 +239,7 @@ namespace {
|
||||
|
||||
size_t buflen = LWS_PRE + (FRAME_SIZE_8000 * desiredSampling / 8000 * channels * 1000 / RTP_PACKETIZATION_PERIOD * nAudioBufferSecs);
|
||||
|
||||
drachtio::AudioPipe* ap = new drachtio::AudioPipe(tech_pvt->sessionId, host, port, path, sslFlags,
|
||||
AudioPipe* ap = new AudioPipe(tech_pvt->sessionId, host, port, path, sslFlags,
|
||||
buflen, read_impl.decoded_bytes_per_packet, username, password, bugname, eventCallback);
|
||||
if (!ap) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error allocating AudioPipe\n");
|
||||
@@ -374,17 +374,13 @@ extern "C" {
|
||||
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE ;
|
||||
//LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
drachtio::AudioPipe::initialize(mySubProtocolName, logs, lws_logger);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_audio_fork successfully initialized\n");
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
AudioPipe::initialize(mySubProtocolName, logs, lws_logger);
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t fork_cleanup() {
|
||||
bool cleanup = false;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_audio_fork unloading..\n");
|
||||
|
||||
cleanup = drachtio::AudioPipe::deinitialize();
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_audio_fork unloaded status %d\n", cleanup);
|
||||
cleanup = AudioPipe::deinitialize();
|
||||
if (cleanup == true) {
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
@@ -424,7 +420,7 @@ extern "C" {
|
||||
|
||||
switch_status_t fork_session_connect(void **ppUserData) {
|
||||
private_t *tech_pvt = static_cast<private_t *>(*ppUserData);
|
||||
drachtio::AudioPipe *pAudioPipe = static_cast<drachtio::AudioPipe*>(tech_pvt->pAudioPipe);
|
||||
AudioPipe *pAudioPipe = static_cast<AudioPipe*>(tech_pvt->pAudioPipe);
|
||||
pAudioPipe->connect();
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
@@ -442,7 +438,7 @@ extern "C" {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) fork_session_cleanup\n", id);
|
||||
|
||||
if (!tech_pvt) return SWITCH_STATUS_FALSE;
|
||||
drachtio::AudioPipe *pAudioPipe = static_cast<drachtio::AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
AudioPipe *pAudioPipe = static_cast<AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
|
||||
switch_mutex_lock(tech_pvt->mutex);
|
||||
|
||||
@@ -485,7 +481,7 @@ extern "C" {
|
||||
private_t* tech_pvt = (private_t*) switch_core_media_bug_get_user_data(bug);
|
||||
|
||||
if (!tech_pvt) return SWITCH_STATUS_FALSE;
|
||||
drachtio::AudioPipe *pAudioPipe = static_cast<drachtio::AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
AudioPipe *pAudioPipe = static_cast<AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
if (pAudioPipe && text) pAudioPipe->bufferForSending(text);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
@@ -520,7 +516,7 @@ extern "C" {
|
||||
|
||||
tech_pvt->graceful_shutdown = 1;
|
||||
|
||||
drachtio::AudioPipe *pAudioPipe = static_cast<drachtio::AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
AudioPipe *pAudioPipe = static_cast<AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
if (pAudioPipe) pAudioPipe->do_graceful_shutdown();
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
@@ -539,8 +535,8 @@ extern "C" {
|
||||
switch_mutex_unlock(tech_pvt->mutex);
|
||||
return SWITCH_TRUE;
|
||||
}
|
||||
drachtio::AudioPipe *pAudioPipe = static_cast<drachtio::AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
if (pAudioPipe->getLwsState() != drachtio::AudioPipe::LWS_CLIENT_CONNECTED) {
|
||||
AudioPipe *pAudioPipe = static_cast<AudioPipe *>(tech_pvt->pAudioPipe);
|
||||
if (pAudioPipe->getLwsState() != AudioPipe::LWS_CLIENT_CONNECTED) {
|
||||
switch_mutex_unlock(tech_pvt->mutex);
|
||||
return SWITCH_TRUE;
|
||||
}
|
||||
|
||||
@@ -48,15 +48,13 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
AudioPipe* ap = findPendingConnect(wsi);
|
||||
if (ap) {
|
||||
std::string apiKey = ap->getApiKey();
|
||||
if (apiKey.length() > 0) {
|
||||
unsigned char **p = (unsigned char **)in, *end = (*p) + len;
|
||||
char b[256];
|
||||
memset(b, 0, sizeof(b));
|
||||
strcpy(b,"Token ");
|
||||
strcpy(b + 6, apiKey.c_str());
|
||||
unsigned char **p = (unsigned char **)in, *end = (*p) + len;
|
||||
char b[256];
|
||||
memset(b, 0, sizeof(b));
|
||||
strcpy(b,"Token ");
|
||||
strcpy(b + 6, apiKey.c_str());
|
||||
|
||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_AUTHORIZATION, (unsigned char *)b, strlen(b), p, end)) return -1;
|
||||
}
|
||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_AUTHORIZATION, (unsigned char *)b, strlen(b), p, end)) return -1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -73,7 +71,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR: %s, response status %d\n", in ? (char *)in : "(null)", rc);
|
||||
if (ap) {
|
||||
ap->m_state = LWS_CLIENT_FAILED;
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), deepgram::AudioPipe::CONNECT_FAIL, (char *) in, ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECT_FAIL, (char *) in, ap->isFinished());
|
||||
}
|
||||
else {
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR unable to find wsi %p..\n", wsi);
|
||||
@@ -84,12 +82,11 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
case LWS_CALLBACK_CLIENT_ESTABLISHED:
|
||||
{
|
||||
AudioPipe* ap = findAndRemovePendingConnect(wsi);
|
||||
|
||||
if (ap) {
|
||||
*ppAp = ap;
|
||||
ap->m_vhd = vhd;
|
||||
ap->m_state = LWS_CLIENT_CONNECTED;
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), deepgram::AudioPipe::CONNECT_SUCCESS, NULL, ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECT_SUCCESS, NULL, ap->isFinished());
|
||||
}
|
||||
else {
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_ESTABLISHED %s unable to find wsi %p..\n", ap->m_uuid.c_str(), wsi);
|
||||
@@ -99,7 +96,6 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
case LWS_CALLBACK_CLIENT_CLOSED:
|
||||
{
|
||||
AudioPipe* ap = *ppAp;
|
||||
|
||||
if (!ap) {
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CLOSED %s unable to find wsi %p..\n", ap->m_uuid.c_str(), wsi);
|
||||
return 0;
|
||||
@@ -108,12 +104,12 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
// closed by us
|
||||
|
||||
lwsl_debug("%s socket closed by us\n", ap->m_uuid.c_str());
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), deepgram::AudioPipe::CONNECTION_CLOSED_GRACEFULLY, NULL, ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECTION_CLOSED_GRACEFULLY, NULL, ap->isFinished());
|
||||
}
|
||||
else if (ap->m_state == LWS_CLIENT_CONNECTED) {
|
||||
// closed by far end
|
||||
lwsl_info("%s socket closed by far end\n", ap->m_uuid.c_str());
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), deepgram::AudioPipe::CONNECTION_DROPPED, NULL, ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECTION_DROPPED, NULL, ap->isFinished());
|
||||
}
|
||||
ap->m_state = LWS_CLIENT_DISCONNECTED;
|
||||
ap->setClosed();
|
||||
@@ -129,7 +125,6 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
case LWS_CALLBACK_CLIENT_RECEIVE:
|
||||
{
|
||||
AudioPipe* ap = *ppAp;
|
||||
|
||||
if (!ap) {
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_RECEIVE %s unable to find wsi %p..\n", ap->m_uuid.c_str(), wsi);
|
||||
return 0;
|
||||
@@ -176,7 +171,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
if (lws_is_final_fragment(wsi)) {
|
||||
if (nullptr != ap->m_recv_buf) {
|
||||
std::string msg((char *)ap->m_recv_buf, ap->m_recv_buf_ptr - ap->m_recv_buf);
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), deepgram::AudioPipe::MESSAGE, msg.c_str(), ap->isFinished());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::MESSAGE, msg.c_str(), ap->isFinished());
|
||||
if (nullptr != ap->m_recv_buf) free(ap->m_recv_buf);
|
||||
}
|
||||
ap->m_recv_buf = ap->m_recv_buf_ptr = nullptr;
|
||||
@@ -189,7 +184,6 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
case LWS_CALLBACK_CLIENT_WRITEABLE:
|
||||
{
|
||||
AudioPipe* ap = *ppAp;
|
||||
|
||||
if (!ap) {
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_WRITEABLE %s unable to find wsi %p..\n", ap->m_uuid.c_str(), wsi);
|
||||
return 0;
|
||||
@@ -257,7 +251,7 @@ static const lws_retry_bo_t retry = {
|
||||
};
|
||||
|
||||
struct lws_context *AudioPipe::context = nullptr;
|
||||
std::thread AudioPipe::serviceThread;
|
||||
std::string AudioPipe::protocolName;
|
||||
std::mutex AudioPipe::mutex_connects;
|
||||
std::mutex AudioPipe::mutex_disconnects;
|
||||
std::mutex AudioPipe::mutex_writes;
|
||||
@@ -426,35 +420,29 @@ bool AudioPipe::lws_service_thread() {
|
||||
|
||||
void AudioPipe::initialize(int loglevel, log_emit_function logger) {
|
||||
|
||||
//lws_set_log_level(loglevel, logger);
|
||||
lws_set_log_level(loglevel, logger);
|
||||
|
||||
lwsl_notice("AudioPipe::initialize starting\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
std::thread t(&AudioPipe::lws_service_thread);
|
||||
stopFlag = false;
|
||||
serviceThread = std::thread(&AudioPipe::lws_service_thread);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
bool AudioPipe::deinitialize() {
|
||||
lwsl_notice("AudioPipe::deinitialize\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
stopFlag = true;
|
||||
if (serviceThread.joinable()) {
|
||||
serviceThread.join();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// instance members
|
||||
AudioPipe::AudioPipe(const char* uuid, const char* bugname, const char* host, unsigned int port, const char* path,
|
||||
size_t bufLen, size_t minFreespace, const char* apiKey, int useTls, notifyHandler_t callback) :
|
||||
m_uuid(uuid), m_host(host), m_port(port), m_path(path), m_finished(false), m_bugname(bugname),
|
||||
AudioPipe::AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path,
|
||||
size_t bufLen, size_t minFreespace, const char* apiKey, notifyHandler_t callback) :
|
||||
m_uuid(uuid), m_host(host), m_port(port), m_path(path), m_finished(false),
|
||||
m_audio_buffer_min_freespace(minFreespace), m_audio_buffer_max_len(bufLen), m_gracefulShutdown(false),
|
||||
m_audio_buffer_write_offset(LWS_PRE), m_recv_buf(nullptr), m_recv_buf_ptr(nullptr), m_useTls(useTls),
|
||||
m_state(LWS_CLIENT_IDLE), m_wsi(nullptr), m_vhd(nullptr), m_callback(callback) {
|
||||
|
||||
if (apiKey) m_apiKey = apiKey;
|
||||
else m_apiKey = "";
|
||||
m_audio_buffer_write_offset(LWS_PRE), m_recv_buf(nullptr), m_recv_buf_ptr(nullptr),
|
||||
m_state(LWS_CLIENT_IDLE), m_wsi(nullptr), m_vhd(nullptr), m_apiKey(apiKey), m_callback(callback) {
|
||||
|
||||
m_audio_buffer = new uint8_t[m_audio_buffer_max_len];
|
||||
}
|
||||
@@ -479,7 +467,8 @@ bool AudioPipe::connect_client(struct lws_per_vhost_data *vhd) {
|
||||
i.path = m_path.c_str();
|
||||
i.host = i.address;
|
||||
i.origin = i.address;
|
||||
if (m_useTls) i.ssl_connection = LCCSCF_USE_SSL;
|
||||
i.ssl_connection = LCCSCF_USE_SSL;
|
||||
//i.protocol = protocolName.c_str();
|
||||
i.pwsi = &(m_wsi);
|
||||
|
||||
m_state = LWS_CLIENT_CONNECTING;
|
||||
|
||||
@@ -13,132 +13,133 @@
|
||||
|
||||
namespace deepgram {
|
||||
|
||||
class AudioPipe {
|
||||
public:
|
||||
enum LwsState_t {
|
||||
LWS_CLIENT_IDLE,
|
||||
LWS_CLIENT_CONNECTING,
|
||||
LWS_CLIENT_CONNECTED,
|
||||
LWS_CLIENT_FAILED,
|
||||
LWS_CLIENT_DISCONNECTING,
|
||||
LWS_CLIENT_DISCONNECTED
|
||||
};
|
||||
enum NotifyEvent_t {
|
||||
CONNECT_SUCCESS,
|
||||
CONNECT_FAIL,
|
||||
CONNECTION_DROPPED,
|
||||
CONNECTION_CLOSED_GRACEFULLY,
|
||||
MESSAGE
|
||||
};
|
||||
typedef void (*log_emit_function)(int level, const char *line);
|
||||
typedef void (*notifyHandler_t)(const char *sessionId, const char* bugname, NotifyEvent_t event, const char* message, bool finished);
|
||||
|
||||
struct lws_per_vhost_data {
|
||||
struct lws_context *context;
|
||||
struct lws_vhost *vhost;
|
||||
const struct lws_protocols *protocol;
|
||||
};
|
||||
|
||||
static void initialize(int loglevel, log_emit_function logger);
|
||||
static bool deinitialize();
|
||||
static bool lws_service_thread();
|
||||
|
||||
// constructor
|
||||
AudioPipe(const char* uuid, const char* bugname, const char* host, unsigned int port, const char* path,
|
||||
size_t bufLen, size_t minFreespace, const char* apiKey, int useTls, notifyHandler_t callback);
|
||||
~AudioPipe();
|
||||
|
||||
LwsState_t getLwsState(void) { return m_state; }
|
||||
std::string& getApiKey(void) {
|
||||
return m_apiKey;
|
||||
}
|
||||
void connect(void);
|
||||
void bufferForSending(const char* text);
|
||||
size_t binarySpaceAvailable(void) {
|
||||
return m_audio_buffer_max_len - m_audio_buffer_write_offset;
|
||||
}
|
||||
size_t binaryMinSpace(void) {
|
||||
return m_audio_buffer_min_freespace;
|
||||
}
|
||||
char * binaryWritePtr(void) {
|
||||
return (char *) m_audio_buffer + m_audio_buffer_write_offset;
|
||||
}
|
||||
void binaryWritePtrAdd(size_t len) {
|
||||
m_audio_buffer_write_offset += len;
|
||||
}
|
||||
void binaryWritePtrResetToZero(void) {
|
||||
m_audio_buffer_write_offset = 0;
|
||||
}
|
||||
void lockAudioBuffer(void) {
|
||||
m_audio_mutex.lock();
|
||||
}
|
||||
void unlockAudioBuffer(void) ;
|
||||
|
||||
void close() ;
|
||||
void finish();
|
||||
void waitForClose();
|
||||
void setClosed() { m_promise.set_value(); }
|
||||
bool isFinished() { return m_finished;}
|
||||
|
||||
// no default constructor or copying
|
||||
AudioPipe() = delete;
|
||||
AudioPipe(const AudioPipe&) = delete;
|
||||
void operator=(const AudioPipe&) = delete;
|
||||
|
||||
private:
|
||||
static std::thread serviceThread;
|
||||
|
||||
static int lws_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
static struct lws_context *context;
|
||||
static std::mutex mutex_connects;
|
||||
static std::mutex mutex_disconnects;
|
||||
static std::mutex mutex_writes;
|
||||
static std::list<AudioPipe*> pendingConnects;
|
||||
static std::list<AudioPipe*> pendingDisconnects;
|
||||
static std::list<AudioPipe*> pendingWrites;
|
||||
static log_emit_function logger;
|
||||
|
||||
static std::mutex mapMutex;
|
||||
static bool stopFlag;
|
||||
|
||||
static AudioPipe* findAndRemovePendingConnect(struct lws *wsi);
|
||||
static AudioPipe* findPendingConnect(struct lws *wsi);
|
||||
static void addPendingConnect(AudioPipe* ap);
|
||||
static void addPendingDisconnect(AudioPipe* ap);
|
||||
static void addPendingWrite(AudioPipe* ap);
|
||||
static void processPendingConnects(lws_per_vhost_data *vhd);
|
||||
static void processPendingDisconnects(lws_per_vhost_data *vhd);
|
||||
static void processPendingWrites(void);
|
||||
|
||||
bool connect_client(struct lws_per_vhost_data *vhd);
|
||||
|
||||
LwsState_t m_state;
|
||||
std::string m_uuid;
|
||||
std::string m_host;
|
||||
unsigned int m_port;
|
||||
std::string m_path;
|
||||
std::string m_metadata;
|
||||
std::mutex m_text_mutex;
|
||||
std::mutex m_audio_mutex;
|
||||
int m_sslFlags;
|
||||
struct lws *m_wsi;
|
||||
uint8_t *m_audio_buffer;
|
||||
size_t m_audio_buffer_max_len;
|
||||
size_t m_audio_buffer_write_offset;
|
||||
size_t m_audio_buffer_min_freespace;
|
||||
uint8_t* m_recv_buf;
|
||||
uint8_t* m_recv_buf_ptr;
|
||||
size_t m_recv_buf_len;
|
||||
struct lws_per_vhost_data* m_vhd;
|
||||
notifyHandler_t m_callback;
|
||||
log_emit_function m_logger;
|
||||
std::string m_apiKey;
|
||||
bool m_gracefulShutdown;
|
||||
bool m_finished;
|
||||
std::string m_bugname;
|
||||
std::promise<void> m_promise;
|
||||
bool m_useTls;
|
||||
class AudioPipe {
|
||||
public:
|
||||
enum LwsState_t {
|
||||
LWS_CLIENT_IDLE,
|
||||
LWS_CLIENT_CONNECTING,
|
||||
LWS_CLIENT_CONNECTED,
|
||||
LWS_CLIENT_FAILED,
|
||||
LWS_CLIENT_DISCONNECTING,
|
||||
LWS_CLIENT_DISCONNECTED
|
||||
};
|
||||
enum NotifyEvent_t {
|
||||
CONNECT_SUCCESS,
|
||||
CONNECT_FAIL,
|
||||
CONNECTION_DROPPED,
|
||||
CONNECTION_CLOSED_GRACEFULLY,
|
||||
MESSAGE
|
||||
};
|
||||
typedef void (*log_emit_function)(int level, const char *line);
|
||||
typedef void (*notifyHandler_t)(const char *sessionId, NotifyEvent_t event, const char* message, bool finished);
|
||||
|
||||
struct lws_per_vhost_data {
|
||||
struct lws_context *context;
|
||||
struct lws_vhost *vhost;
|
||||
const struct lws_protocols *protocol;
|
||||
};
|
||||
|
||||
static void initialize(int loglevel, log_emit_function logger);
|
||||
static bool deinitialize();
|
||||
static bool lws_service_thread();
|
||||
|
||||
// constructor
|
||||
AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path,
|
||||
size_t bufLen, size_t minFreespace, const char* apiKey, notifyHandler_t callback);
|
||||
~AudioPipe();
|
||||
|
||||
LwsState_t getLwsState(void) { return m_state; }
|
||||
std::string& getApiKey(void) {
|
||||
return m_apiKey;
|
||||
}
|
||||
void connect(void);
|
||||
void bufferForSending(const char* text);
|
||||
size_t binarySpaceAvailable(void) {
|
||||
return m_audio_buffer_max_len - m_audio_buffer_write_offset;
|
||||
}
|
||||
size_t binaryMinSpace(void) {
|
||||
return m_audio_buffer_min_freespace;
|
||||
}
|
||||
char * binaryWritePtr(void) {
|
||||
return (char *) m_audio_buffer + m_audio_buffer_write_offset;
|
||||
}
|
||||
void binaryWritePtrAdd(size_t len) {
|
||||
m_audio_buffer_write_offset += len;
|
||||
}
|
||||
void binaryWritePtrResetToZero(void) {
|
||||
m_audio_buffer_write_offset = 0;
|
||||
}
|
||||
void lockAudioBuffer(void) {
|
||||
m_audio_mutex.lock();
|
||||
}
|
||||
void unlockAudioBuffer(void) ;
|
||||
|
||||
void close() ;
|
||||
void finish();
|
||||
void waitForClose();
|
||||
void setClosed() { m_promise.set_value(); }
|
||||
bool isFinished() { return m_finished;}
|
||||
|
||||
// no default constructor or copying
|
||||
AudioPipe() = delete;
|
||||
AudioPipe(const AudioPipe&) = delete;
|
||||
void operator=(const AudioPipe&) = delete;
|
||||
|
||||
private:
|
||||
|
||||
static int lws_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
static unsigned int nchild;
|
||||
static struct lws_context *context;
|
||||
static unsigned int numContexts;
|
||||
static std::string protocolName;
|
||||
static std::mutex mutex_connects;
|
||||
static std::mutex mutex_disconnects;
|
||||
static std::mutex mutex_writes;
|
||||
static std::list<AudioPipe*> pendingConnects;
|
||||
static std::list<AudioPipe*> pendingDisconnects;
|
||||
static std::list<AudioPipe*> pendingWrites;
|
||||
static log_emit_function logger;
|
||||
|
||||
static std::mutex mapMutex;
|
||||
static bool stopFlag;
|
||||
|
||||
static AudioPipe* findAndRemovePendingConnect(struct lws *wsi);
|
||||
static AudioPipe* findPendingConnect(struct lws *wsi);
|
||||
static void addPendingConnect(AudioPipe* ap);
|
||||
static void addPendingDisconnect(AudioPipe* ap);
|
||||
static void addPendingWrite(AudioPipe* ap);
|
||||
static void processPendingConnects(lws_per_vhost_data *vhd);
|
||||
static void processPendingDisconnects(lws_per_vhost_data *vhd);
|
||||
static void processPendingWrites(void);
|
||||
|
||||
bool connect_client(struct lws_per_vhost_data *vhd);
|
||||
|
||||
LwsState_t m_state;
|
||||
std::string m_uuid;
|
||||
std::string m_host;
|
||||
unsigned int m_port;
|
||||
std::string m_path;
|
||||
std::string m_metadata;
|
||||
std::mutex m_text_mutex;
|
||||
std::mutex m_audio_mutex;
|
||||
int m_sslFlags;
|
||||
struct lws *m_wsi;
|
||||
uint8_t *m_audio_buffer;
|
||||
size_t m_audio_buffer_max_len;
|
||||
size_t m_audio_buffer_write_offset;
|
||||
size_t m_audio_buffer_min_freespace;
|
||||
uint8_t* m_recv_buf;
|
||||
uint8_t* m_recv_buf_ptr;
|
||||
size_t m_recv_buf_len;
|
||||
struct lws_per_vhost_data* m_vhd;
|
||||
notifyHandler_t m_callback;
|
||||
log_emit_function m_logger;
|
||||
std::string m_apiKey;
|
||||
bool m_gracefulShutdown;
|
||||
bool m_finished;
|
||||
std::string m_bugname;
|
||||
std::promise<void> m_promise;
|
||||
};
|
||||
|
||||
} // namespace deepgram
|
||||
#endif
|
||||
|
||||
@@ -32,59 +32,56 @@ namespace {
|
||||
static unsigned int idxCallCount = 0;
|
||||
static uint32_t playCount = 0;
|
||||
|
||||
/* deepgram model defaults by language */
|
||||
static const std::unordered_map<std::string, std::string> languageLookupTable = {
|
||||
{"zh", "base"},
|
||||
{"zh-CN", "base"},
|
||||
{"zh-TW", "base"},
|
||||
{"da", "nova-2"},
|
||||
{"da-DK", "nova-2"},
|
||||
{"en", "nova-2"},
|
||||
{"en-US", "nova-2"},
|
||||
{"en-AU", "nova-2"},
|
||||
{"en-GB", "nova-2"},
|
||||
{"en-IN", "nova-2"},
|
||||
{"en-NZ", "nova-2"},
|
||||
{"nl", "nova-2"},
|
||||
{"nl-BE", "nova-2"},
|
||||
{"fr", "nova-2"},
|
||||
{"fr-CA", "nova-2"},
|
||||
{"de", "nova-2"},
|
||||
{"el", "nova-2"},
|
||||
{"hi", "nova-2"},
|
||||
{"hi-Latn", "nova-2"},
|
||||
{"id", "nova-2"},
|
||||
{"it", "nova-2"},
|
||||
{"ja", "enhanced"},
|
||||
{"ko", "nova-2"},
|
||||
{"ko-KR", "nova-2"},
|
||||
{"no", "nova-2"},
|
||||
{"pl", "nova-2"},
|
||||
{"pt","nova-2"},
|
||||
{"pt-BR", "nova-2"},
|
||||
{"ru", "nova-2"},
|
||||
{"es","nova-2"},
|
||||
{"es-419","nova-2"},
|
||||
{"es-LATAM","enhanced"},
|
||||
{"sv", "nova-2"},
|
||||
{"sv-SE", "nova-2"},
|
||||
{"ta", "enhanced"},
|
||||
{"taq", "enhanced"},
|
||||
{"tr", "nova-2"},
|
||||
{"uk", "nova-2"}
|
||||
/* deepgram model / tier defaults by language */
|
||||
struct LanguageInfo {
|
||||
std::string tier;
|
||||
std::string model;
|
||||
};
|
||||
|
||||
static bool getLanguageInfo(const std::string& language, std::string& model) {
|
||||
static const std::unordered_map<std::string, LanguageInfo> languageLookupTable = {
|
||||
{"zh", {"base", "general"}},
|
||||
{"zh-CN", {"base", "general"}},
|
||||
{"zh-TW", {"base", "general"}},
|
||||
{"da", {"enhanced", "general"}},
|
||||
{"en", {"nova", "phonecall"}},
|
||||
{"en-US", {"nova", "phonecall"}},
|
||||
{"en-AU", {"nova", "general"}},
|
||||
{"en-GB", {"nova", "general"}},
|
||||
{"en-IN", {"nova", "general"}},
|
||||
{"en-NZ", {"nova", "general"}},
|
||||
{"nl", {"enhanced", "general"}},
|
||||
{"fr", {"enhanced", "general"}},
|
||||
{"fr-CA", {"base", "general"}},
|
||||
{"de", {"enhanced", "general"}},
|
||||
{"hi", {"enhanced", "general"}},
|
||||
{"hi-Latn", {"base", "general"}},
|
||||
{"id", {"base", "general"}},
|
||||
{"ja", {"enhanced", "general"}},
|
||||
{"ko", {"enhanced", "general"}},
|
||||
{"no", {"enhanced", "general"}},
|
||||
{"pl", {"enhanced", "general"}},
|
||||
{"pt", {"enhanced", "general"}},
|
||||
{"pt-BR", {"enhanced", "general"}},
|
||||
{"pt-PT", {"enhanced", "general"}},
|
||||
{"ru", {"base", "general"}},
|
||||
{"es", {"nova", "general"}},
|
||||
{"es-419", {"nova", "general"}},
|
||||
{"sv", {"enhanced", "general"}},
|
||||
{"ta", {"enhanced", "general"}},
|
||||
{"tr", {"base", "general"}},
|
||||
{"uk", {"base", "general"}}
|
||||
};
|
||||
|
||||
static bool getLanguageInfo(const std::string& language, LanguageInfo& info) {
|
||||
auto it = languageLookupTable.find(language);
|
||||
if (it != languageLookupTable.end()) {
|
||||
model = it->second;
|
||||
info = it->second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char* emptyTranscript =
|
||||
"\"is_final\":false,\"speech_final\":false,\"channel\":{\"alternatives\":[{\"transcript\":\"\",\"confidence\":0.0,\"words\":[]}]}";
|
||||
static const char* emptyTranscript = "{\"alternatives\":[{\"transcript\":\"\",\"confidence\":0.0,\"words\":[]}]}";
|
||||
|
||||
static void reaper(private_t *tech_pvt) {
|
||||
std::shared_ptr<deepgram::AudioPipe> pAp;
|
||||
@@ -147,22 +144,26 @@ namespace {
|
||||
const char *var ;
|
||||
const char *model = switch_channel_get_variable(channel, "DEEPGRAM_SPEECH_MODEL");
|
||||
const char *customModel = switch_channel_get_variable(channel, "DEEPGRAM_SPEECH_CUSTOM_MODEL");
|
||||
const char *tier = switch_channel_get_variable(channel, "DEEPGRAM_SPEECH_TIER") ;
|
||||
std::ostringstream oss;
|
||||
LanguageInfo info;
|
||||
|
||||
oss << "/v1/listen?";
|
||||
|
||||
/* make best choice by language if model not supplied*/
|
||||
if (!model && !customModel) {
|
||||
std::string defaultModel;
|
||||
if (getLanguageInfo(language, defaultModel)) {
|
||||
oss << "&model=" << defaultModel;
|
||||
if (!tier && !model && !customModel) {
|
||||
/* make best choice by language */
|
||||
if (getLanguageInfo(language, info)) {
|
||||
oss << "tier=" << info.tier << "&model=" << info.model;
|
||||
}
|
||||
else {
|
||||
oss << "model=base"; // most widely supported, though not ideal
|
||||
oss << "tier=base&model=general"; // most widely supported, though not ideal
|
||||
}
|
||||
}
|
||||
else if (model) oss << "&model=" << model;
|
||||
else if (customModel) oss << "&model=" << customModel;
|
||||
else {
|
||||
if (tier) oss << "tier=" << tier;
|
||||
if (model) oss << "&model=" << model;
|
||||
if (customModel) oss << "&model=" << customModel;
|
||||
}
|
||||
|
||||
if (var = switch_channel_get_variable(channel, "DEEPGRAM_SPEECH_MODEL_VERSION")) {
|
||||
oss << "&version";
|
||||
@@ -243,6 +244,9 @@ namespace {
|
||||
oss << "&tag=";
|
||||
oss << var;
|
||||
}
|
||||
if (interim) {
|
||||
oss << "&interim_results=true";
|
||||
}
|
||||
if (var = switch_channel_get_variable(channel, "DEEPGRAM_SPEECH_ENDPOINTING")) {
|
||||
oss << "&endpointing=";
|
||||
oss << var;
|
||||
@@ -250,11 +254,6 @@ namespace {
|
||||
if (var = switch_channel_get_variable(channel, "DEEPGRAM_SPEECH_UTTERANCE_END_MS")) {
|
||||
oss << "&utterance_end_ms=";
|
||||
oss << var;
|
||||
interim = 1;
|
||||
//this requires interim_results=true (https://developers.deepgram.com/docs/understanding-end-of-speech-detection)
|
||||
}
|
||||
if (interim) {
|
||||
oss << "&interim_results=true";
|
||||
}
|
||||
if (var = switch_channel_get_variable(channel, "DEEPGRAM_SPEECH_VAD_TURNOFF")) {
|
||||
oss << "&vad_turnoff=";
|
||||
@@ -266,18 +265,17 @@ namespace {
|
||||
return path;
|
||||
}
|
||||
|
||||
static void eventCallback(const char* sessionId, const char* bugname,
|
||||
deepgram::AudioPipe::NotifyEvent_t event, const char* message, bool finished) {
|
||||
static void eventCallback(const char* sessionId, deepgram::AudioPipe::NotifyEvent_t event, const char* message, bool finished) {
|
||||
switch_core_session_t* session = switch_core_session_locate(sessionId);
|
||||
if (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, bugname);
|
||||
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
|
||||
if (bug) {
|
||||
private_t* tech_pvt = (private_t*) switch_core_media_bug_get_user_data(bug);
|
||||
if (tech_pvt) {
|
||||
switch (event) {
|
||||
case deepgram::AudioPipe::CONNECT_SUCCESS:
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "connection (%s) successful\n", tech_pvt->bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "connection successful\n");
|
||||
tech_pvt->responseHandler(session, TRANSCRIBE_EVENT_CONNECT_SUCCESS, NULL, tech_pvt->bugname, finished);
|
||||
break;
|
||||
case deepgram::AudioPipe::CONNECT_FAIL:
|
||||
@@ -287,19 +285,19 @@ namespace {
|
||||
json << "{\"reason\":\"" << message << "\"}";
|
||||
tech_pvt->pAudioPipe = nullptr;
|
||||
tech_pvt->responseHandler(session, TRANSCRIBE_EVENT_CONNECT_FAIL, (char *) json.str().c_str(), tech_pvt->bugname, finished);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "connection (%s) failed: %s\n", message, tech_pvt->bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "connection failed: %s\n", message);
|
||||
}
|
||||
break;
|
||||
case deepgram::AudioPipe::CONNECTION_DROPPED:
|
||||
// first thing: we can no longer access the AudioPipe
|
||||
tech_pvt->pAudioPipe = nullptr;
|
||||
tech_pvt->responseHandler(session, TRANSCRIBE_EVENT_DISCONNECT, NULL, tech_pvt->bugname, finished);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connection (%s) dropped from far end\n", tech_pvt->bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connection dropped from far end\n");
|
||||
break;
|
||||
case deepgram::AudioPipe::CONNECTION_CLOSED_GRACEFULLY:
|
||||
// first thing: we can no longer access the AudioPipe
|
||||
tech_pvt->pAudioPipe = nullptr;
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connection (%s) closed gracefully\n", tech_pvt->bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connection closed gracefully\n");
|
||||
break;
|
||||
case deepgram::AudioPipe::MESSAGE:
|
||||
if( strstr(message, emptyTranscript)) {
|
||||
@@ -307,7 +305,7 @@ namespace {
|
||||
}
|
||||
else {
|
||||
tech_pvt->responseHandler(session, TRANSCRIBE_EVENT_RESULTS, message, tech_pvt->bugname, finished);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "deepgram message (%s): %s\n", tech_pvt->bugname, message);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "deepgram message: %s\n", message);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -325,7 +323,6 @@ namespace {
|
||||
char* bugname, responseHandler_t responseHandler) {
|
||||
|
||||
int err;
|
||||
int useTls = true;
|
||||
switch_codec_implementation_t read_impl;
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
|
||||
@@ -338,51 +335,26 @@ namespace {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "path: %s\n", path.c_str());
|
||||
|
||||
strncpy(tech_pvt->sessionId, switch_core_session_get_uuid(session), MAX_SESSION_ID);
|
||||
|
||||
const char* endpoint = switch_channel_get_variable(channel, "DEEPGRAM_URI");
|
||||
if (endpoint != nullptr) {
|
||||
std::string ep(endpoint);
|
||||
|
||||
useTls = switch_true(switch_channel_get_variable(channel, "DEEPGRAM_USE_TLS"));
|
||||
|
||||
size_t pos = ep.find(':');
|
||||
if (pos != std::string::npos) {
|
||||
std::string host = ep.substr(0, pos);
|
||||
std::string port = ep.substr(pos + 1);
|
||||
strncpy(tech_pvt->host, host.c_str(), MAX_WS_URL_LEN);
|
||||
tech_pvt->port = ::atoi(port.c_str());
|
||||
}
|
||||
else {
|
||||
strncpy(tech_pvt->host, ep.c_str(), MAX_WS_URL_LEN);
|
||||
tech_pvt->port = 443;
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO,
|
||||
"connecting to deepgram on-prem %s port %d, using tls? (%s)\n", tech_pvt->host, tech_pvt->port, useTls ? "yes" : "no");
|
||||
}
|
||||
else {
|
||||
strncpy(tech_pvt->host, "api.deepgram.com", MAX_WS_URL_LEN);
|
||||
tech_pvt->port = 443;
|
||||
}
|
||||
|
||||
strncpy(tech_pvt->host, "api.deepgram.com", MAX_WS_URL_LEN);
|
||||
tech_pvt->port = 443;
|
||||
strncpy(tech_pvt->path, path.c_str(), MAX_PATH_LEN);
|
||||
tech_pvt->sampling = desiredSampling;
|
||||
tech_pvt->responseHandler = responseHandler;
|
||||
tech_pvt->channels = channels;
|
||||
tech_pvt->id = ++idxCallCount;
|
||||
tech_pvt->buffer_overrun_notified = 0;
|
||||
strncpy(tech_pvt->bugname, bugname, MAX_BUG_LEN);
|
||||
|
||||
size_t buflen = LWS_PRE + (FRAME_SIZE_8000 * desiredSampling / 8000 * channels * 1000 / RTP_PACKETIZATION_PERIOD * nAudioBufferSecs);
|
||||
|
||||
const char* apiKey = switch_channel_get_variable(channel, "DEEPGRAM_API_KEY");
|
||||
if (!apiKey && defaultApiKey) apiKey = defaultApiKey;
|
||||
else if (!apiKey && endpoint == nullptr) {
|
||||
else if (!apiKey) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "no deepgram api key provided\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
deepgram::AudioPipe* ap = new deepgram::AudioPipe(tech_pvt->sessionId, bugname, tech_pvt->host, tech_pvt->port, tech_pvt->path,
|
||||
buflen, read_impl.decoded_bytes_per_packet, apiKey, useTls, eventCallback);
|
||||
deepgram::AudioPipe* ap = new deepgram::AudioPipe(tech_pvt->sessionId, tech_pvt->host, tech_pvt->port, tech_pvt->path,
|
||||
buflen, read_impl.decoded_bytes_per_packet, apiKey, eventCallback);
|
||||
if (!ap) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error allocating AudioPipe\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
@@ -428,8 +400,7 @@ extern "C" {
|
||||
switch_status_t dg_transcribe_init() {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_deepgram_transcribe: audio buffer (in secs): %d secs\n", nAudioBufferSecs);
|
||||
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE;
|
||||
// | LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE || LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
|
||||
deepgram::AudioPipe::initialize(logs, lws_logger);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "AudioPipe::initialize completed\n");
|
||||
@@ -484,7 +455,7 @@ extern "C" {
|
||||
|
||||
switch_status_t dg_transcribe_session_stop(switch_core_session_t *session,int channelIsClosing, char* bugname) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, bugname);
|
||||
switch_media_bug_t *bug = (switch_media_bug_t*) switch_channel_get_private(channel, MY_BUG_NAME);
|
||||
if (!bug) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "dg_transcribe_session_stop: no bug - websocket conection already closed\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
|
||||
@@ -45,6 +45,7 @@ static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data,
|
||||
{
|
||||
private_t *tech_pvt = (private_t*) switch_core_media_bug_get_user_data(bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Got SWITCH_ABC_TYPE_CLOSE.\n");
|
||||
|
||||
dg_transcribe_session_stop(session, 1, tech_pvt->bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Finished SWITCH_ABC_TYPE_CLOSE.\n");
|
||||
}
|
||||
@@ -73,7 +74,7 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
||||
void *pUserData;
|
||||
uint32_t samples_per_second;
|
||||
|
||||
if (switch_channel_get_private(channel, bugname)) {
|
||||
if (switch_channel_get_private(channel, MY_BUG_NAME)) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing bug from previous transcribe\n");
|
||||
do_stop(session, bugname);
|
||||
}
|
||||
@@ -93,7 +94,7 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
||||
if ((status = switch_core_media_bug_add(session, "dg_transcribe", NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
switch_channel_set_private(channel, bugname, bug);
|
||||
switch_channel_set_private(channel, MY_BUG_NAME, bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "added media bug for dg transcribe\n");
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
@@ -104,16 +105,13 @@ static switch_status_t do_stop(switch_core_session_t *session, char* bugname)
|
||||
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, bugname);
|
||||
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 transcribe.\n");
|
||||
status = dg_transcribe_session_stop(session, 0, bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "stopped transcribe.\n");
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "do_stop: did not find bugname %s.\n", bugname);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -143,7 +141,7 @@ SWITCH_STANDARD_API(dg_transcribe_function)
|
||||
if ((lsession = switch_core_session_locate(argv[0]))) {
|
||||
if (!strcasecmp(argv[1], "stop")) {
|
||||
char *bugname = argc > 2 ? argv[2] : MY_BUG_NAME;
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "stop transcribing %s\n", bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "stop transcribing\n");
|
||||
status = do_stop(lsession, bugname);
|
||||
} else if (!strcasecmp(argv[1], "start")) {
|
||||
char* lang = argv[2];
|
||||
@@ -153,8 +151,7 @@ SWITCH_STANDARD_API(dg_transcribe_function)
|
||||
flags |= SMBF_WRITE_STREAM ;
|
||||
flags |= SMBF_STEREO;
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "start transcribing %s %s %s\n",
|
||||
lang, interim ? "interim": "complete", bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "start transcribing %s %s\n", lang, interim ? "interim": "complete");
|
||||
status = start_capture(lsession, flags, lang, interim, bugname);
|
||||
}
|
||||
switch_core_session_rwunlock(lsession);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
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.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
include $(top_srcdir)/build/modmake.rulesam
|
||||
MODNAME=mod_elevenlabs_tts
|
||||
|
||||
mod_LTLIBRARIES = mod_elevenlabs_tts.la
|
||||
mod_elevenlabs_tts_la_SOURCES = mod_elevenlabs_tts.c elevenlabs_glue.cpp
|
||||
mod_elevenlabs_tts_la_CFLAGS = $(AM_CFLAGS)
|
||||
mod_elevenlabs_tts_la_LIBADD = $(switch_builddir)/libfreeswitch.la
|
||||
mod_elevenlabs_tts_la_LDFLAGS = -avoid-version -module -no-undefined -shared `pkg-config --libs boost` -lstdc++
|
||||
@@ -1,23 +0,0 @@
|
||||
# mod_google_tts
|
||||
|
||||
A Freeswitch module that allows Google Text-to-Speech API to be used as a tts provider.
|
||||
|
||||
## API
|
||||
|
||||
### Commands
|
||||
This freeswitch module does not add any new commands, per se. Rather, it integrates into the Freeswitch TTS interface such that it is invoked when an application uses the mod_dptools `speak` command with a tts engine of `google_tts` and a voice equal to the language code associated to one of the [supported Wavenet voices](https://cloud.google.com/text-to-speech/docs/voices)
|
||||
|
||||
### Events
|
||||
None.
|
||||
|
||||
## Usage
|
||||
When using [drachtio-fsrmf](https://www.npmjs.com/package/drachtio-fsmrf), you can access this functionality via the speak method on the 'endpoint' object.
|
||||
```js
|
||||
ep.speak({
|
||||
ttsEngine: 'google_tts',
|
||||
voice: 'en-GB-Wavenet-A',
|
||||
text: 'This aggression will not stand'
|
||||
});
|
||||
```
|
||||
## Examples
|
||||
[google_tts.js](../../examples/google_tts.js)
|
||||
@@ -1,982 +0,0 @@
|
||||
#include <switch.h>
|
||||
#include <switch_json.h>
|
||||
#include <g711.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <sys/stat.h>
|
||||
#include <iostream>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/pool/object_pool.hpp>
|
||||
#include <boost/bind/bind.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "mod_elevenlabs_tts.h"
|
||||
|
||||
#define TXNID_LEN (255)
|
||||
#define URL_LEN (1024)
|
||||
#define HTTP_BODY_LEN (16384)
|
||||
#define BUFFER_GROW_SIZE (8192)
|
||||
|
||||
#include <sstream>
|
||||
|
||||
//#define SWITCH_UUID_FORMATTED_LENGTH (256)
|
||||
|
||||
typedef boost::circular_buffer<uint16_t> CircularBuffer_t;
|
||||
|
||||
/* Global information, common to all connections */
|
||||
typedef struct
|
||||
{
|
||||
CURLM *multi;
|
||||
int still_running;
|
||||
} GlobalInfo_t;
|
||||
static GlobalInfo_t global;
|
||||
|
||||
/* Information associated with a specific easy handle */
|
||||
typedef struct
|
||||
{
|
||||
CURL *easy;
|
||||
elevenlabs_t* elevenlabs;
|
||||
char* body;
|
||||
struct curl_slist *hdr_list;
|
||||
GlobalInfo_t *global;
|
||||
char error[CURL_ERROR_SIZE];
|
||||
FILE* file;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> startTime;
|
||||
bool flushed;
|
||||
} ConnInfo_t;
|
||||
|
||||
/* static singletons shared by all sessions */
|
||||
static boost::object_pool<ConnInfo_t> pool ;
|
||||
static boost::asio::io_service io_service;
|
||||
static boost::asio::deadline_timer timer(io_service);
|
||||
static std::map<curl_socket_t, boost::asio::ip::tcp::socket *> socket_map;
|
||||
static std::thread worker_thread ;
|
||||
|
||||
/* statics */
|
||||
|
||||
static std::string fullDirPath;
|
||||
|
||||
static void timer_cb(const boost::system::error_code & error, GlobalInfo_t *g);
|
||||
|
||||
static bool removeDirectory(const std::string &dirPath) {
|
||||
DIR *dir = opendir(dirPath.c_str());
|
||||
if (dir) {
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
std::string entryPath = dirPath + "/" + entry->d_name;
|
||||
struct stat statbuf;
|
||||
if (!stat(entryPath.c_str(), &statbuf)) {
|
||||
if (S_ISDIR(statbuf.st_mode)) {
|
||||
if (std::string(entry->d_name) != "." && std::string(entry->d_name) != "..") {
|
||||
if (!removeDirectory(entryPath)) {
|
||||
closedir(dir);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (unlink(entryPath.c_str()) != 0) {
|
||||
closedir(dir);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
return rmdir(dirPath.c_str()) == 0;
|
||||
}
|
||||
|
||||
static bool parseHeader(const std::string& str, std::string& header, std::string& value) {
|
||||
std::vector<std::string> parts;
|
||||
boost::split(parts, str, boost::is_any_of(":"), boost::token_compress_on);
|
||||
|
||||
if (parts.size() != 2)
|
||||
return false;
|
||||
|
||||
header = boost::trim_copy(parts[0]);
|
||||
value = boost::trim_copy(parts[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string secondsToMillisecondsString(double seconds) {
|
||||
// Convert to milliseconds
|
||||
double milliseconds = seconds * 1000.0;
|
||||
|
||||
// Truncate to remove fractional part
|
||||
long milliseconds_long = static_cast<long>(milliseconds);
|
||||
|
||||
// Convert to string
|
||||
return std::to_string(milliseconds_long);
|
||||
}
|
||||
|
||||
static void cleanupConn(ConnInfo_t *conn) {
|
||||
auto el = conn->elevenlabs;
|
||||
|
||||
if( conn->hdr_list ) {
|
||||
curl_slist_free_all(conn->hdr_list);
|
||||
conn->hdr_list = nullptr ;
|
||||
}
|
||||
curl_easy_cleanup(conn->easy);
|
||||
|
||||
if (conn->file) {
|
||||
if (fclose(conn->file) != 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cleanupConn: error closing audio cache file\n");
|
||||
}
|
||||
conn->file = nullptr ;
|
||||
}
|
||||
|
||||
el->conn = nullptr ;
|
||||
el->draining = 1;
|
||||
|
||||
memset(conn, 0, sizeof(ConnInfo_t));
|
||||
pool.destroy(conn) ;
|
||||
}
|
||||
|
||||
int multi_timer_cb(CURLM *multi, long timeout_ms, GlobalInfo_t *g) {
|
||||
|
||||
/* cancel running timer */
|
||||
timer.cancel();
|
||||
|
||||
if(timeout_ms >= 0) {
|
||||
// from libcurl 7.88.1-10+deb12u4 does not allow call curl_multi_socket_action or curl_multi_perform in curl_multi callback directly
|
||||
timer.expires_from_now(boost::posix_time::millisec(timeout_ms ? timeout_ms : 1));
|
||||
timer.async_wait(boost::bind(&timer_cb, boost::placeholders::_1, g));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static CURL* createEasyHandle(void) {
|
||||
CURL* easy = curl_easy_init();
|
||||
if(!easy) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "curl_easy_init() failed!\n");
|
||||
return nullptr ;
|
||||
}
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(easy, CURLOPT_USERAGENT, "jambonz/0.8.5");
|
||||
|
||||
// set connect timeout to 3 seconds and total timeout to 109 seconds
|
||||
curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT_MS, 3000L);
|
||||
curl_easy_setopt(easy, CURLOPT_TIMEOUT, 10L);
|
||||
|
||||
return easy ;
|
||||
}
|
||||
|
||||
int mcode_test(const char *where, CURLMcode code) {
|
||||
if(CURLM_OK != code) {
|
||||
const char *s;
|
||||
switch(code) {
|
||||
case CURLM_CALL_MULTI_PERFORM:
|
||||
s = "CURLM_CALL_MULTI_PERFORM";
|
||||
break;
|
||||
case CURLM_BAD_HANDLE:
|
||||
s = "CURLM_BAD_HANDLE";
|
||||
break;
|
||||
case CURLM_BAD_EASY_HANDLE:
|
||||
s = "CURLM_BAD_EASY_HANDLE";
|
||||
break;
|
||||
case CURLM_OUT_OF_MEMORY:
|
||||
s = "CURLM_OUT_OF_MEMORY";
|
||||
break;
|
||||
case CURLM_INTERNAL_ERROR:
|
||||
s = "CURLM_INTERNAL_ERROR";
|
||||
break;
|
||||
case CURLM_UNKNOWN_OPTION:
|
||||
s = "CURLM_UNKNOWN_OPTION";
|
||||
break;
|
||||
case CURLM_LAST:
|
||||
s = "CURLM_LAST";
|
||||
break;
|
||||
default:
|
||||
s = "CURLM_unknown";
|
||||
break;
|
||||
case CURLM_BAD_SOCKET:
|
||||
s = "CURLM_BAD_SOCKET";
|
||||
break;
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mcode_test ERROR: %s returns %s:%d\n", where, s, code);
|
||||
|
||||
return -1;
|
||||
}
|
||||
return 0 ;
|
||||
}
|
||||
|
||||
static void remsock(int *f, GlobalInfo_t *g) {
|
||||
if(f) {
|
||||
free(f);
|
||||
f = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for completed transfers, and remove their easy handles */
|
||||
void check_multi_info(GlobalInfo_t *g) {
|
||||
CURLMsg *msg;
|
||||
int msgs_left;
|
||||
ConnInfo_t *conn;
|
||||
CURL *easy;
|
||||
CURLcode res;
|
||||
|
||||
while((msg = curl_multi_info_read(g->multi, &msgs_left))) {
|
||||
if(msg->msg == CURLMSG_DONE) {
|
||||
long response_code;
|
||||
double namelookup=0, connect=0, total=0 ;
|
||||
char *ct = NULL ;
|
||||
|
||||
easy = msg->easy_handle;
|
||||
res = msg->data.result;
|
||||
curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
|
||||
curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
curl_easy_getinfo(easy, CURLINFO_CONTENT_TYPE, &ct);
|
||||
|
||||
curl_easy_getinfo(easy, CURLINFO_NAMELOOKUP_TIME, &namelookup);
|
||||
curl_easy_getinfo(easy, CURLINFO_CONNECT_TIME, &connect);
|
||||
curl_easy_getinfo(easy, CURLINFO_TOTAL_TIME, &total);
|
||||
|
||||
auto el = conn->elevenlabs;
|
||||
el->response_code = response_code;
|
||||
if (ct) el->ct = strdup(ct);
|
||||
|
||||
std::string name_lookup_ms = secondsToMillisecondsString(namelookup);
|
||||
std::string connect_ms = secondsToMillisecondsString(connect);
|
||||
std::string final_response_time_ms = secondsToMillisecondsString(total);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
|
||||
"mod_elevenlabs_tts: response: %ld, content-type %s,"
|
||||
"dns(ms): %" CURL_FORMAT_CURL_OFF_T ".%06ld, "
|
||||
"connect(ms): %" CURL_FORMAT_CURL_OFF_T ".%06ld, "
|
||||
"total(ms): %" CURL_FORMAT_CURL_OFF_T ".%06ld\n",
|
||||
response_code, ct,
|
||||
(long)(namelookup), (long)(fmod(namelookup, 1.0) * 1000000),
|
||||
(long)(connect), (long)(fmod(connect, 1.0) * 1000000),
|
||||
(long)(total), (long)(fmod(total, 1.0) * 1000000));
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "name lookup time: %s\n", name_lookup_ms.c_str());
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "connect time: %s\n", connect_ms.c_str());
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "final response time: %s\n", final_response_time_ms.c_str());
|
||||
|
||||
el->name_lookup_time_ms = strdup(name_lookup_ms.c_str());
|
||||
el->connect_time_ms = strdup(connect_ms.c_str());
|
||||
el->final_response_time_ms = strdup(final_response_time_ms.c_str());
|
||||
|
||||
curl_multi_remove_handle(g->multi, easy);
|
||||
cleanupConn(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Called by asio when there is an action on a socket */
|
||||
static void event_cb(GlobalInfo_t *g, curl_socket_t s, int action, const boost::system::error_code & error, int *fdp) {
|
||||
int f = *fdp;
|
||||
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "event_cb socket %#X has action %d\n", s, action) ;
|
||||
|
||||
// Socket already POOL REMOVED.
|
||||
if (f == CURL_POLL_REMOVE) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "event_cb socket %#X removed\n", s);
|
||||
remsock(fdp, g);
|
||||
return;
|
||||
}
|
||||
|
||||
if(socket_map.find(s) == socket_map.end()) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "event_cb: socket %#X already closed\n, s");
|
||||
return;
|
||||
}
|
||||
|
||||
/* make sure the event matches what are wanted */
|
||||
if(f == action || f == CURL_POLL_INOUT) {
|
||||
if(error) {
|
||||
action = CURL_CSELECT_ERR;
|
||||
}
|
||||
CURLMcode rc = curl_multi_socket_action(g->multi, s, action, &g->still_running);
|
||||
|
||||
mcode_test("event_cb: curl_multi_socket_action", rc);
|
||||
check_multi_info(g);
|
||||
|
||||
if(g->still_running <= 0) {
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
/* keep on watching.
|
||||
* the socket may have been closed and/or fdp may have been changed
|
||||
* in curl_multi_socket_action(), so check them both */
|
||||
if(!error && socket_map.find(s) != socket_map.end() &&
|
||||
(f == action || f == CURL_POLL_INOUT)) {
|
||||
boost::asio::ip::tcp::socket *tcp_socket = socket_map.find(s)->second;
|
||||
|
||||
if(action == CURL_POLL_IN) {
|
||||
tcp_socket->async_read_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
action, boost::placeholders::_1, fdp));
|
||||
}
|
||||
if(action == CURL_POLL_OUT) {
|
||||
tcp_socket->async_write_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
action, boost::placeholders::_1, fdp));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* socket functions */
|
||||
static void setsock(int *fdp, curl_socket_t s, CURL *e, int act, int oldact, GlobalInfo_t *g) {
|
||||
std::map<curl_socket_t, boost::asio::ip::tcp::socket *>::iterator it = socket_map.find(s);
|
||||
|
||||
if(it == socket_map.end()) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "setsock: socket %#X not found\n, s");
|
||||
return;
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::socket * tcp_socket = it->second;
|
||||
|
||||
*fdp = act;
|
||||
|
||||
if(act == CURL_POLL_IN) {
|
||||
if(oldact != CURL_POLL_IN && oldact != CURL_POLL_INOUT) {
|
||||
tcp_socket->async_read_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
CURL_POLL_IN, boost::placeholders::_1, fdp));
|
||||
}
|
||||
}
|
||||
else if(act == CURL_POLL_OUT) {
|
||||
if(oldact != CURL_POLL_OUT && oldact != CURL_POLL_INOUT) {
|
||||
tcp_socket->async_write_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
CURL_POLL_OUT, boost::placeholders::_1, fdp));
|
||||
}
|
||||
}
|
||||
else if(act == CURL_POLL_INOUT) {
|
||||
if(oldact != CURL_POLL_IN && oldact != CURL_POLL_INOUT) {
|
||||
tcp_socket->async_read_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
CURL_POLL_IN, boost::placeholders::_1, fdp));
|
||||
}
|
||||
if(oldact != CURL_POLL_OUT && oldact != CURL_POLL_INOUT) {
|
||||
tcp_socket->async_write_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
CURL_POLL_OUT, boost::placeholders::_1, fdp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo_t *g) {
|
||||
/* fdp is used to store current action */
|
||||
int *fdp = (int *) calloc(sizeof(int), 1);
|
||||
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "addsock socket %#X\n", s);
|
||||
|
||||
setsock(fdp, s, easy, action, 0, g);
|
||||
curl_multi_assign(g->multi, s, fdp);
|
||||
}
|
||||
|
||||
|
||||
static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) {
|
||||
GlobalInfo_t *g = &global;
|
||||
|
||||
int *actionp = (int *) sockp;
|
||||
static const char *whatstr[] = { "none", "IN", "OUT", "INOUT", "REMOVE"};
|
||||
|
||||
if(what == CURL_POLL_REMOVE) {
|
||||
*actionp = what;
|
||||
}
|
||||
else {
|
||||
if(!actionp) {
|
||||
addsock(s, e, what, g);
|
||||
}
|
||||
else {
|
||||
setsock(actionp, s, e, what, *actionp, g);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called by asio when our timeout expires */
|
||||
static void timer_cb(const boost::system::error_code & error, GlobalInfo_t *g)
|
||||
{
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "timer_cb\n");
|
||||
|
||||
if(!error) {
|
||||
CURLMcode rc = curl_multi_socket_action(g->multi, CURL_SOCKET_TIMEOUT, 0, &g->still_running);
|
||||
mcode_test("timer_cb: curl_multi_socket_action", rc);
|
||||
check_multi_info(g);
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<uint16_t> convert_ulaw_to_linear(uint8_t *data, size_t len) {
|
||||
std::vector<uint16_t> linear_data;
|
||||
linear_data.reserve(len); // Reserve space to avoid reallocation
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
linear_data.push_back(ulaw_to_linear(data[i]));
|
||||
}
|
||||
return linear_data;
|
||||
}
|
||||
|
||||
|
||||
/* CURLOPT_WRITEFUNCTION */
|
||||
static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
||||
bool fireEvent = false;
|
||||
uint8_t *data = (uint8_t *) ptr;
|
||||
size_t bytes_received = size * nmemb;
|
||||
auto el = conn->elevenlabs;
|
||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) el->circularBuffer;
|
||||
std::vector<uint16_t> pcm_data;
|
||||
|
||||
if (conn->flushed) {
|
||||
/* this will abort the transfer */
|
||||
return 0;
|
||||
}
|
||||
{
|
||||
switch_mutex_lock(el->mutex);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "write_cb: received data, response %ld\n",
|
||||
el->response_code);
|
||||
|
||||
if (el->response_code > 0 && el->response_code != 200) {
|
||||
std::string body((char *) ptr, bytes_received);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: received body %s\n", body.c_str());
|
||||
el->err_msg = strdup(body.c_str());
|
||||
switch_mutex_unlock(el->mutex);
|
||||
return 0;
|
||||
}
|
||||
pcm_data = convert_ulaw_to_linear(data, bytes_received);
|
||||
|
||||
/* and write to the file */
|
||||
size_t bytesResampled = pcm_data.size() * sizeof(uint16_t);
|
||||
if (conn->file) fwrite(pcm_data.data(), sizeof(uint16_t), pcm_data.size(), conn->file);
|
||||
|
||||
// Resize the buffer if necessary
|
||||
if (cBuffer->capacity() - cBuffer->size() < (bytesResampled / sizeof(uint16_t))) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "write_cb growing buffer\n");
|
||||
|
||||
//TODO: if buffer exceeds some max size, return CURL_WRITEFUNC_ERROR to abort the transfer
|
||||
cBuffer->set_capacity(cBuffer->size() + std::max((bytesResampled / sizeof(uint16_t)), (size_t)BUFFER_GROW_SIZE));
|
||||
}
|
||||
|
||||
/* Push the data into the buffer */
|
||||
cBuffer->insert(cBuffer->end(), pcm_data.data(), pcm_data.data() + pcm_data.size());
|
||||
|
||||
if (0 == el->reads++) {
|
||||
fireEvent = true;
|
||||
|
||||
/* trime leading 50ms = 400 samples since on elevenlabs it is silence */
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "write_cb triming 400 samples (50ms) silence from start of buffer\n");
|
||||
//cBuffer->erase(cBuffer->begin(), cBuffer->begin() + 400);
|
||||
}
|
||||
switch_mutex_unlock(el->mutex);
|
||||
}
|
||||
if (fireEvent && el->session_id) {
|
||||
auto endTime = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - conn->startTime);
|
||||
auto time_to_first_byte_ms = std::to_string(duration.count());
|
||||
switch_core_session_t* session = switch_core_session_locate(el->session_id);
|
||||
if (session) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
if (channel) {
|
||||
switch_event_t *event;
|
||||
if (switch_event_create(&event, SWITCH_EVENT_PLAYBACK_START) == SWITCH_STATUS_SUCCESS) {
|
||||
switch_channel_event_set_data(channel, event);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "write_cb: firing playback-started\n");
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Playback-File-Type", "tts_stream");
|
||||
if (el->reported_latency) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_reported_latency_ms", el->reported_latency);
|
||||
}
|
||||
if (el->request_id) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_request_id", el->request_id);
|
||||
}
|
||||
if (el->history_item_id) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_history_item_id", el->history_item_id);
|
||||
}
|
||||
if (el->name_lookup_time_ms) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_name_lookup_time_ms", el->name_lookup_time_ms);
|
||||
}
|
||||
if (el->connect_time_ms) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_connect_time_ms", el->connect_time_ms);
|
||||
}
|
||||
if (el->final_response_time_ms) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_final_response_time_ms", el->final_response_time_ms);
|
||||
}
|
||||
if (el->voice_name) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_voice_name", el->voice_name);
|
||||
}
|
||||
if (el->model_id) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_model_id", el->model_id);
|
||||
}
|
||||
if (el->optimize_streaming_latency) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_optimize_streaming_latency", el->optimize_streaming_latency);
|
||||
}
|
||||
if (el->cache_filename) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", el->cache_filename);
|
||||
}
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_time_to_first_byte_ms", time_to_first_byte_ms.c_str());
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: failed to create event\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: channel not found\n");
|
||||
}
|
||||
switch_core_session_rwunlock(session);
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: session %s not found\n", el->session_id);
|
||||
}
|
||||
}
|
||||
return bytes_received;
|
||||
}
|
||||
|
||||
static size_t header_callback(char *buffer, size_t size, size_t nitems, ConnInfo_t *conn) {
|
||||
size_t bytes_received = size * nitems;
|
||||
const std::string prefix = "HTTP/2 ";
|
||||
elevenlabs_t* el = conn->elevenlabs;
|
||||
std::string header, value;
|
||||
std::string input(buffer, bytes_received);
|
||||
if (parseHeader(input, header, value)) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "recv header: %s with value %s\n", header.c_str(), value.c_str());
|
||||
if (0 == header.compare("tts-latency-ms")) el->reported_latency = strdup(value.c_str());
|
||||
else if (0 == header.compare("request-id")) el->request_id = strdup(value.c_str());
|
||||
else if (0 == header.compare("history-item-id")) el->history_item_id = strdup(value.c_str());
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "recv header: %s\n", input.c_str());
|
||||
if (input.rfind(prefix, 0) == 0) {
|
||||
try {
|
||||
el->response_code = std::stoi(input.substr(prefix.length()));
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "parsed response code: %ld\n", el->response_code);
|
||||
} catch (const std::invalid_argument& e) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "header_callback: invalid response code %s\n", input.substr(prefix.length()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytes_received;
|
||||
}
|
||||
|
||||
/* CURLOPT_OPENSOCKETFUNCTION */
|
||||
static curl_socket_t opensocket(void *clientp, curlsocktype purpose, struct curl_sockaddr *address) {
|
||||
curl_socket_t sockfd = CURL_SOCKET_BAD;
|
||||
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "opensocket: %d\n", purpose);
|
||||
/* restrict to IPv4 */
|
||||
if(purpose == CURLSOCKTYPE_IPCXN && address->family == AF_INET) {
|
||||
/* create a tcp socket object */
|
||||
boost::asio::ip::tcp::socket *tcp_socket = new boost::asio::ip::tcp::socket(io_service);
|
||||
|
||||
/* open it and get the native handle*/
|
||||
boost::system::error_code ec;
|
||||
tcp_socket->open(boost::asio::ip::tcp::v4(), ec);
|
||||
|
||||
if(ec) {
|
||||
/* An error occurred */
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't open socket [%ld][%s]\n", ec, ec.message().c_str());
|
||||
}
|
||||
else {
|
||||
sockfd = tcp_socket->native_handle();
|
||||
|
||||
/* save it for monitoring */
|
||||
socket_map.insert(std::pair<curl_socket_t, boost::asio::ip::tcp::socket *>(sockfd, tcp_socket));
|
||||
}
|
||||
}
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/* CURLOPT_CLOSESOCKETFUNCTION */
|
||||
static int close_socket(void *clientp, curl_socket_t item) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "close_socket : %#X\n", item);
|
||||
|
||||
std::map<curl_socket_t, boost::asio::ip::tcp::socket *>::iterator it = socket_map.find(item);
|
||||
if(it != socket_map.end()) {
|
||||
delete it->second;
|
||||
socket_map.erase(it);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void threadFunc() {
|
||||
/* to make sure the event loop doesn't terminate when there is no work to do */
|
||||
io_service.reset() ;
|
||||
boost::asio::io_service::work work(io_service);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_elevenlabs_tts threadFunc - starting\n");
|
||||
|
||||
for(;;) {
|
||||
|
||||
try {
|
||||
io_service.run() ;
|
||||
break ;
|
||||
}
|
||||
catch( std::exception& e) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mod_elevenlabs_tts threadFunc - Error: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_elevenlabs_tts threadFunc - ending\n");
|
||||
}
|
||||
|
||||
/* C api bindings */
|
||||
|
||||
extern "C" {
|
||||
switch_status_t elevenlabs_speech_load() {
|
||||
memset(&global, 0, sizeof(GlobalInfo_t));
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "elevenlabs_speech_loadng..\n");
|
||||
|
||||
global.multi = curl_multi_init();
|
||||
|
||||
if (!global.multi) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "curl_multi_init() failed, exiting!\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
curl_multi_setopt(global.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
|
||||
curl_multi_setopt(global.multi, CURLMOPT_SOCKETDATA, &global);
|
||||
curl_multi_setopt(global.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
|
||||
curl_multi_setopt(global.multi, CURLMOPT_TIMERDATA, &global);
|
||||
curl_multi_setopt(global.multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
||||
|
||||
/* create temp folder for cache files */
|
||||
const char* baseDir = std::getenv("JAMBONZ_TMP_CACHE_FOLDER");
|
||||
if (!baseDir) {
|
||||
baseDir = "/var/";
|
||||
}
|
||||
if (strcmp(baseDir, "/") == 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to create folder %s\n", baseDir);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
fullDirPath = std::string(baseDir) + "jambonz-tts-cache-files";
|
||||
|
||||
// Create the directory with read, write, and execute permissions for everyone
|
||||
mode_t oldMask = umask(0);
|
||||
int result = mkdir(fullDirPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
|
||||
umask(oldMask);
|
||||
if (result != 0) {
|
||||
if (errno != EEXIST) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to create folder %s\n", fullDirPath.c_str());
|
||||
fullDirPath = "";
|
||||
}
|
||||
else switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "folder %s already exists\n", fullDirPath.c_str());
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "created folder %s\n", fullDirPath.c_str());
|
||||
}
|
||||
|
||||
/* start worker thread that handles transfers*/
|
||||
std::thread t(threadFunc) ;
|
||||
worker_thread.swap( t ) ;
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "elevenlabs_speech_loaded..\n");
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t elevenlabs_speech_unload() {
|
||||
/* stop the ASIO IO service */
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "elevenlabs_speech_unload: stopping io service\n");
|
||||
io_service.stop();
|
||||
|
||||
/* Join the worker thread */
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "elevenlabs_speech_unload: wait for worker thread to complete\n");
|
||||
if (worker_thread.joinable()) {
|
||||
worker_thread.join();
|
||||
}
|
||||
|
||||
/* cleanup curl multi handle*/
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "elevenlabs_speech_unload: release curl multi\n");
|
||||
curl_multi_cleanup(global.multi);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "elevenlabs_speech_unload: completed\n");
|
||||
|
||||
/*
|
||||
if (!fullDirPath.empty()) {
|
||||
if (removeDirectory(fullDirPath)) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "elevenlabs_speech_unload: removed folder %s\n", fullDirPath.c_str());
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "elevenlabs_speech_unload: failed to remove folder %s\n", fullDirPath.c_str());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t elevenlabs_speech_open() {
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t elevenlabs_speech_feed_tts(elevenlabs_t* el, char* text, switch_speech_flag_t *flags) {
|
||||
CURLMcode rc;
|
||||
const int MAX_CHARS = 20;
|
||||
char tempText[MAX_CHARS + 4]; // +4 for the ellipsis and null terminator
|
||||
|
||||
if (strlen(text) > MAX_CHARS) {
|
||||
strncpy(tempText, text, MAX_CHARS);
|
||||
strcpy(tempText + MAX_CHARS, "...");
|
||||
} else {
|
||||
strcpy(tempText, text);
|
||||
}
|
||||
|
||||
/* open cache file */
|
||||
if (el->cache_audio && fullDirPath.length() > 0) {
|
||||
switch_uuid_t uuid;
|
||||
char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1];
|
||||
char outfile[512] = "";
|
||||
int fd;
|
||||
|
||||
switch_uuid_get(&uuid);
|
||||
switch_uuid_format(uuid_str, &uuid);
|
||||
|
||||
switch_snprintf(outfile, sizeof(outfile), "%s%s%s.r8", fullDirPath.c_str(), SWITCH_PATH_SEPARATOR, uuid_str);
|
||||
el->cache_filename = strdup(outfile);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "writing audio cache file to %s\n", el->cache_filename);
|
||||
|
||||
mode_t oldMask = umask(0);
|
||||
fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
umask(oldMask);
|
||||
if (fd == -1 ) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening cache file %s: %s\n", outfile, strerror(errno));
|
||||
}
|
||||
else {
|
||||
el->file = fdopen(fd, "wb");
|
||||
if (!el->file) {
|
||||
close(fd);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening cache file %s: %s\n", outfile, strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "elevenlabs_speech_feed_tts: text %s\n", text);
|
||||
|
||||
if (!el->api_key) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "eevenlabs_speech_feed_tts: no api_key provided\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
if (!el->model_id) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "elevenlabs_speech_feed_tts: no model_id provided\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
/* format url*/
|
||||
std::string url;
|
||||
std::ostringstream url_stream;
|
||||
url_stream << "https://api.elevenlabs.io/v1/text-to-speech/" << el->voice_name << "/stream?";
|
||||
url_stream << "optimize_streaming_latency=" << el->optimize_streaming_latency << "&output_format=ulaw_8000";
|
||||
url = url_stream.str();
|
||||
|
||||
/* create the JSON body */
|
||||
cJSON * jResult = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(jResult, "model_id", el->model_id);
|
||||
cJSON_AddStringToObject(jResult, "text", text);
|
||||
if (el->similarity_boost || el->style || el->use_speaker_boost || el->stability) {
|
||||
cJSON * jVoiceSettings = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(jResult, "voice_settings", jVoiceSettings);
|
||||
if (el->similarity_boost) {
|
||||
cJSON_AddStringToObject(jVoiceSettings, "similarity_boost", el->similarity_boost);
|
||||
}
|
||||
if (el->style) {
|
||||
cJSON_AddStringToObject(jVoiceSettings, "style", el->style);
|
||||
}
|
||||
if (el->use_speaker_boost) {
|
||||
cJSON_AddStringToObject(jVoiceSettings, "use_speaker_boost", el->use_speaker_boost);
|
||||
}
|
||||
if (el->stability) {
|
||||
cJSON_AddStringToObject(jVoiceSettings, "stability", el->stability);
|
||||
}
|
||||
}
|
||||
char *json = cJSON_PrintUnformatted(jResult);;
|
||||
|
||||
cJSON_Delete(jResult);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "elevenlabs_speech_feed_tts: [%s] [%s]\n", url.c_str(), tempText);
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "elevenlabs_speech_feed_tts: %s\n", json);
|
||||
|
||||
ConnInfo_t *conn = pool.malloc() ;
|
||||
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "allocated Conn %p\n", conn);
|
||||
|
||||
CURL* easy = createEasyHandle();
|
||||
|
||||
el->conn = (void *) conn ;
|
||||
conn->elevenlabs = el;
|
||||
conn->easy = easy;
|
||||
conn->global = &global;
|
||||
conn->hdr_list = NULL ;
|
||||
conn->file = el->file;
|
||||
conn->body = json;
|
||||
conn->flushed = false;
|
||||
|
||||
el->circularBuffer = (void *) new CircularBuffer_t(8192);
|
||||
|
||||
std::ostringstream api_key_stream;
|
||||
api_key_stream << "xi-api-key: " << el->api_key;
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(easy, CURLOPT_WRITEDATA, conn);
|
||||
curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, conn->error);
|
||||
curl_easy_setopt(easy, CURLOPT_PRIVATE, conn);
|
||||
curl_easy_setopt(easy, CURLOPT_VERBOSE, 0L);
|
||||
curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1L);
|
||||
curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, header_callback);
|
||||
curl_easy_setopt(easy, CURLOPT_HEADERDATA, conn);
|
||||
|
||||
/* call this function to get a socket */
|
||||
curl_easy_setopt(easy, CURLOPT_OPENSOCKETFUNCTION, opensocket);
|
||||
|
||||
/* call this function to close a socket */
|
||||
curl_easy_setopt(easy, CURLOPT_CLOSESOCKETFUNCTION, close_socket);
|
||||
|
||||
conn->hdr_list = curl_slist_append(conn->hdr_list, api_key_stream.str().c_str());
|
||||
conn->hdr_list = curl_slist_append(conn->hdr_list, "Content-Type: application/json");
|
||||
curl_easy_setopt(easy, CURLOPT_HTTPHEADER, conn->hdr_list);
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
||||
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||
|
||||
rc = curl_multi_add_handle(global.multi, conn->easy);
|
||||
mcode_test("new_conn: curl_multi_add_handle", rc);
|
||||
|
||||
/* start a timer to measure the duration until we receive first byte of audio */
|
||||
conn->startTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "elevenlabs_speech_feed_tts: called curl_multi_add_handle\n");
|
||||
|
||||
/* note that the add_handle() will set a time-out to trigger very soon so
|
||||
that the necessary socket_action() call will be called by this app */
|
||||
|
||||
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t elevenlabs_speech_read_tts(elevenlabs_t* el, void *data, size_t *datalen, switch_speech_flag_t *flags) {
|
||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) el->circularBuffer;
|
||||
std::vector<uint16_t> pcm_data;
|
||||
|
||||
{
|
||||
switch_mutex_lock(el->mutex);
|
||||
ConnInfo_t *conn = (ConnInfo_t *) el->conn;
|
||||
|
||||
if (el->response_code > 0 && el->response_code != 200) {
|
||||
switch_mutex_unlock(el->mutex);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "elevenlabs_speech_read_tts, returning failure\n") ;
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
if (conn && conn->flushed) {
|
||||
switch_mutex_unlock(el->mutex);
|
||||
return SWITCH_STATUS_BREAK;
|
||||
}
|
||||
if (cBuffer->empty()) {
|
||||
if (el->draining) {
|
||||
switch_mutex_unlock(el->mutex);
|
||||
return SWITCH_STATUS_BREAK;
|
||||
}
|
||||
/* no audio available yet so send silence */
|
||||
memset(data, 255, *datalen);
|
||||
switch_mutex_unlock(el->mutex);
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
size_t size = std::min((*datalen/2), cBuffer->size());
|
||||
pcm_data.insert(pcm_data.end(), cBuffer->begin(), cBuffer->begin() + size);
|
||||
cBuffer->erase(cBuffer->begin(), cBuffer->begin() + size);
|
||||
switch_mutex_unlock(el->mutex);
|
||||
}
|
||||
|
||||
memcpy(data, pcm_data.data(), pcm_data.size() * sizeof(uint16_t));
|
||||
*datalen = pcm_data.size() * sizeof(uint16_t);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t elevenlabs_speech_flush_tts(elevenlabs_t* el) {
|
||||
bool download_complete = el->response_code == 200;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "elevenlabs_speech_flush_tts, download complete? %s\n", download_complete ? "yes" : "no") ;
|
||||
|
||||
ConnInfo_t *conn = (ConnInfo_t *) el->conn;
|
||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) el->circularBuffer;
|
||||
delete cBuffer;
|
||||
el->circularBuffer = nullptr ;
|
||||
|
||||
if (conn) {
|
||||
conn->flushed = true;
|
||||
if (!download_complete) {
|
||||
if (conn->file) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "closing audio cache file %s because download was interrupted\n", el->cache_filename);
|
||||
if (fclose(conn->file) != 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "error closing audio cache file\n");
|
||||
}
|
||||
conn->file = nullptr ;
|
||||
}
|
||||
|
||||
if (el->cache_filename) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing audio cache file %s because download was interrupted\n", el->cache_filename);
|
||||
if (unlink(el->cache_filename) != 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cleanupConn: error removing audio cache file %s: %d:%s\n",
|
||||
el->cache_filename, errno, strerror(errno));
|
||||
}
|
||||
free(el->cache_filename);
|
||||
el->cache_filename = nullptr ;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (el->session_id) {
|
||||
switch_core_session_t* session = switch_core_session_locate(el->session_id);
|
||||
if (session) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
if (channel) {
|
||||
switch_event_t *event;
|
||||
if (switch_event_create(&event, SWITCH_EVENT_PLAYBACK_STOP) == SWITCH_STATUS_SUCCESS) {
|
||||
switch_channel_event_set_data(channel, event);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Playback-File-Type", "tts_stream");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_elevenlabs_response_code", std::to_string(el->response_code).c_str());
|
||||
if (el->cache_filename && el->response_code == 200) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", el->cache_filename);
|
||||
}
|
||||
if (el->response_code != 200 && el->err_msg) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_error", el->err_msg);
|
||||
}
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: failed to create event\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: channel not found\n");
|
||||
}
|
||||
switch_core_session_rwunlock(session);
|
||||
}
|
||||
}
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t elevenlabs_speech_close(elevenlabs_t* el) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "elevenlabs_speech_close\n") ;
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#ifndef __ELEVENLABS_GLUE_H__
|
||||
#define __ELEVENLABS_GLUE_H__
|
||||
|
||||
switch_status_t elevenlabs_speech_load();
|
||||
switch_status_t elevenlabs_speech_open(elevenlabs_t* elevenlabs);
|
||||
switch_status_t elevenlabs_speech_feed_tts(elevenlabs_t* elevenlabs, char* text, switch_speech_flag_t *flags);
|
||||
switch_status_t elevenlabs_speech_read_tts(elevenlabs_t* elevenlabs, void *data, size_t *datalen, switch_speech_flag_t *flags);
|
||||
switch_status_t elevenlabs_speech_flush_tts(elevenlabs_t* elevenlabs);
|
||||
switch_status_t elevenlabs_speech_close(elevenlabs_t* elevenlabs);
|
||||
switch_status_t elevenlabs_speech_unload();
|
||||
|
||||
#endif
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* mod_elevenlabs_tts.c -- Google GRPC-based text to speech
|
||||
*
|
||||
*/
|
||||
#include "mod_elevenlabs_tts.h"
|
||||
#include "elevenlabs_glue.h"
|
||||
|
||||
SWITCH_MODULE_LOAD_FUNCTION(mod_elevenlabs_tts_load);
|
||||
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_elevenlabs_tts_shutdown);
|
||||
SWITCH_MODULE_DEFINITION(mod_elevenlabs_tts, mod_elevenlabs_tts_load, mod_elevenlabs_tts_shutdown, NULL);
|
||||
|
||||
static void clearElevenlabs(elevenlabs_t* el, int freeAll) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "clearElevenlabs\n");
|
||||
if (el->api_key) free(el->api_key);
|
||||
if (el->model_id) free(el->model_id);
|
||||
if (el->similarity_boost) free(el->similarity_boost);
|
||||
if (el->stability) free(el->stability);
|
||||
if (el->style) free(el->style);
|
||||
if (el->use_speaker_boost) free(el->use_speaker_boost);
|
||||
if (el->optimize_streaming_latency) free(el->optimize_streaming_latency);
|
||||
if (el->ct) free(el->ct);
|
||||
if (el->reported_latency) free(el->reported_latency);
|
||||
if (el->request_id) free(el->request_id);
|
||||
if (el->history_item_id) free(el->history_item_id);
|
||||
if (el->err_msg) free(el->err_msg);
|
||||
if (el->name_lookup_time_ms) free(el->name_lookup_time_ms);
|
||||
if (el->connect_time_ms) free(el->connect_time_ms);
|
||||
if (el->final_response_time_ms) free(el->final_response_time_ms);
|
||||
if (el->cache_filename) free(el->cache_filename);
|
||||
|
||||
el->api_key = NULL;
|
||||
el->model_id = NULL;
|
||||
el->similarity_boost = NULL;
|
||||
el->stability = NULL;
|
||||
el->style = NULL;
|
||||
el->use_speaker_boost = NULL;
|
||||
el->optimize_streaming_latency = NULL;
|
||||
el->ct = NULL;
|
||||
el->reported_latency = NULL;
|
||||
el->request_id = NULL;
|
||||
el->history_item_id = NULL;
|
||||
el->err_msg = NULL;
|
||||
el->name_lookup_time_ms = NULL;
|
||||
el->connect_time_ms = NULL;
|
||||
el->final_response_time_ms = NULL;
|
||||
el->cache_filename = NULL;
|
||||
|
||||
el->file = NULL;
|
||||
|
||||
if (freeAll) {
|
||||
if (el->voice_name) free(el->voice_name);
|
||||
if (el->session_id) free(el->session_id);
|
||||
el->voice_name = NULL;
|
||||
el->session_id = NULL;
|
||||
}
|
||||
}
|
||||
static elevenlabs_t * createOrRetrievePrivateData(switch_speech_handle_t *sh) {
|
||||
elevenlabs_t *el = (elevenlabs_t *) sh->private_info;
|
||||
if (!el) {
|
||||
el = switch_core_alloc(sh->memory_pool, sizeof(*el));
|
||||
sh->private_info = el;
|
||||
memset(el, 0, sizeof(*el));
|
||||
switch_mutex_init(&el->mutex, SWITCH_MUTEX_NESTED, sh->memory_pool);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "allocated elevenlabs_t\n");
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* this is called when FreeSWITCH loads the module
|
||||
* if the module is retrieved from cache this will not be called
|
||||
* therefore, probably best not to connect to elevenlabs yet
|
||||
* Note: if cached, the voice will be provide as a param
|
||||
*/
|
||||
static switch_status_t ell_speech_open(switch_speech_handle_t *sh, const char *voice_name, int rate, int channels, switch_speech_flag_t *flags)
|
||||
{
|
||||
|
||||
elevenlabs_t *el = createOrRetrievePrivateData(sh);
|
||||
el->voice_name = strdup(voice_name);
|
||||
el->rate = rate;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "ell_speech_open voice: %s, rate %d, channels %d\n", voice_name, rate, channels);
|
||||
return elevenlabs_speech_open(el);
|
||||
}
|
||||
|
||||
static switch_status_t ell_speech_close(switch_speech_handle_t *sh, switch_speech_flag_t *flags)
|
||||
{
|
||||
switch_status_t rc;
|
||||
elevenlabs_t *el = (elevenlabs_t *) sh->private_info;
|
||||
assert(el != NULL);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "ell_speech_close\n");
|
||||
|
||||
switch_mutex_destroy(el->mutex);
|
||||
|
||||
rc = elevenlabs_speech_close(el);
|
||||
clearElevenlabs(el, 1);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeswitch will call this function to feed us text to speak
|
||||
*/
|
||||
static switch_status_t ell_speech_feed_tts(switch_speech_handle_t *sh, char *text, switch_speech_flag_t *flags)
|
||||
{
|
||||
elevenlabs_t *el = createOrRetrievePrivateData(sh);
|
||||
el->draining = 0;
|
||||
el->reads = 0;
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "ell_speech_feed_tts\n");
|
||||
|
||||
return elevenlabs_speech_feed_tts(el, text, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called at the end, not sure exactly what we need to do here..
|
||||
*/
|
||||
static void ell_speech_flush_tts(switch_speech_handle_t *sh)
|
||||
{
|
||||
elevenlabs_t *el = (elevenlabs_t *) sh->private_info;
|
||||
//assert(el != NULL);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "ell_speech_flush_tts\n");
|
||||
elevenlabs_speech_flush_tts(el);
|
||||
|
||||
clearElevenlabs(el, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeswitch calls periodically to get some rendered audio in L16 format. We can provide up to 8k of audio at a time.
|
||||
*/
|
||||
static switch_status_t ell_speech_read_tts(switch_speech_handle_t *sh, void *data, size_t *datalen, switch_speech_flag_t *flags)
|
||||
{
|
||||
elevenlabs_t *el = (elevenlabs_t *) sh->private_info;
|
||||
return elevenlabs_speech_read_tts(el, data, datalen, flags);
|
||||
}
|
||||
|
||||
static void ell_text_param_tts(switch_speech_handle_t *sh, char *param, const char *val)
|
||||
{
|
||||
elevenlabs_t *el = createOrRetrievePrivateData(sh);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "ell_text_param_tts: %s=%s\n", param, val);
|
||||
|
||||
if (0 == strcmp(param, "voice")) {
|
||||
if (el->voice_name) free(el->voice_name);
|
||||
el->voice_name = strdup(val);
|
||||
}
|
||||
else if (0 == strcmp(param, "api_key")) {
|
||||
if (el->api_key) free(el->api_key);
|
||||
el->api_key = strdup(val);
|
||||
}
|
||||
else if (0 == strcmp(param, "model_id")) {
|
||||
if (el->model_id) free(el->model_id);
|
||||
el->model_id = strdup(val);
|
||||
}
|
||||
else if (0 == strcmp(param, "similarity_boost")) {
|
||||
if (el->similarity_boost) free(el->similarity_boost);
|
||||
el->similarity_boost = strdup(val);
|
||||
}
|
||||
else if (0 == strcmp(param, "stability")) {
|
||||
if (el->stability) free(el->stability);
|
||||
el->stability = strdup(val);
|
||||
}
|
||||
else if (0 == strcmp(param, "style")) {
|
||||
if (el->style) free(el->style);
|
||||
el->style = strdup(val);
|
||||
}
|
||||
else if (0 == strcmp(param, "use_speaker_boost")) {
|
||||
if (el->use_speaker_boost) free(el->use_speaker_boost);
|
||||
el->use_speaker_boost = strdup(val);
|
||||
}
|
||||
else if (0 == strcmp(param, "optimize_streaming_latency")) {
|
||||
if (el->optimize_streaming_latency) free(el->optimize_streaming_latency);
|
||||
el->optimize_streaming_latency = strdup(val);
|
||||
}
|
||||
else if (0 == strcmp(param, "write_cache_file") && switch_true(val)) {
|
||||
el->cache_audio = 1;
|
||||
}
|
||||
else if (0 == strcmp(param, "session-uuid")) {
|
||||
if (el->session_id) free(el->session_id);
|
||||
el->session_id = strdup(val);
|
||||
}
|
||||
}
|
||||
|
||||
static void ell_numeric_param_tts(switch_speech_handle_t *sh, char *param, int val)
|
||||
{
|
||||
}
|
||||
|
||||
static void ell_float_param_tts(switch_speech_handle_t *sh, char *param, double val)
|
||||
{
|
||||
}
|
||||
|
||||
SWITCH_MODULE_LOAD_FUNCTION(mod_elevenlabs_tts_load)
|
||||
{
|
||||
switch_speech_interface_t *speech_interface;
|
||||
|
||||
/* connect my internal structure to the blank pointer passed to me */
|
||||
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
|
||||
speech_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_SPEECH_INTERFACE);
|
||||
speech_interface->interface_name = "elevenlabs";
|
||||
speech_interface->speech_open = ell_speech_open;
|
||||
speech_interface->speech_close = ell_speech_close;
|
||||
speech_interface->speech_feed_tts = ell_speech_feed_tts;
|
||||
speech_interface->speech_read_tts = ell_speech_read_tts;
|
||||
speech_interface->speech_flush_tts = ell_speech_flush_tts;
|
||||
speech_interface->speech_text_param_tts = ell_text_param_tts;
|
||||
speech_interface->speech_numeric_param_tts = ell_numeric_param_tts;
|
||||
speech_interface->speech_float_param_tts = ell_float_param_tts;
|
||||
|
||||
return elevenlabs_speech_load();
|
||||
}
|
||||
|
||||
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_elevenlabs_tts_shutdown)
|
||||
{
|
||||
return elevenlabs_speech_unload();
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef __MOD_ELEVENLABS_TTS_H__
|
||||
#define __MOD_ELEVENLABS_TTS_H__
|
||||
|
||||
#include <switch.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct elevenlabs_data {
|
||||
char *session_id;
|
||||
char *voice_name;
|
||||
char* api_key;
|
||||
char* model_id;
|
||||
char* similarity_boost;
|
||||
char* stability;
|
||||
char* style;
|
||||
char* use_speaker_boost;
|
||||
char* optimize_streaming_latency;
|
||||
|
||||
/* result data */
|
||||
long response_code;
|
||||
char *ct;
|
||||
char *reported_latency;
|
||||
char *request_id;
|
||||
char *history_item_id;
|
||||
char *name_lookup_time_ms;
|
||||
char *connect_time_ms;
|
||||
char *final_response_time_ms;
|
||||
char *err_msg;
|
||||
char *cache_filename;
|
||||
|
||||
int rate;
|
||||
|
||||
void *conn;
|
||||
FILE *file;
|
||||
switch_mutex_t *mutex;
|
||||
void *circularBuffer;
|
||||
int draining;
|
||||
int reads;
|
||||
int cache_audio;
|
||||
};
|
||||
|
||||
typedef struct elevenlabs_data elevenlabs_t;
|
||||
|
||||
#endif
|
||||
@@ -380,16 +380,9 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
|
||||
struct cap_cb *cb = (struct cap_cb *) obj;
|
||||
GStreamer* streamer = (GStreamer *) cb->streamer;
|
||||
|
||||
std::thread::id this_id = std::this_thread::get_id();
|
||||
std::stringstream ss;
|
||||
ss << this_id;
|
||||
std::string id_str = ss.str();
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "grpc_read_thread %s starting\n", id_str.c_str()) ;
|
||||
|
||||
bool connected = streamer->waitForConnect();
|
||||
if (!connected) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "grpc_read_thread %s thread exiting since we didnt connect\n", id_str.c_str()) ;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "google transcribe grpc read thread exiting since we didnt connect\n") ;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -398,14 +391,14 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
|
||||
while (streamer->read(&response)) { // Returns false when no more to read.
|
||||
switch_core_session_t* session = switch_core_session_locate(cb->sessionId);
|
||||
if (!session) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "grpc_read_thread: %s session %s is gone!\n", id_str.c_str(), cb->sessionId) ;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "grpc_read_thread: session %s is gone!\n", cb->sessionId) ;
|
||||
return nullptr;
|
||||
}
|
||||
count++;
|
||||
auto speech_event_type = response.speech_event_type();
|
||||
if (response.has_error()) {
|
||||
Status status = response.error();
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "grpc_read_thread: %s error %s (%d)\n", id_str.c_str(), status.message().c_str(), status.code()) ;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "grpc_read_thread: error %s (%d)\n", status.message().c_str(), status.code()) ;
|
||||
cJSON* json = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(json, "type", "error");
|
||||
cJSON_AddStringToObject(json, "error", status.message().c_str());
|
||||
@@ -451,7 +444,7 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
|
||||
|
||||
if (alternative.words_size() > 0) {
|
||||
cJSON * jWords = cJSON_CreateArray();
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: %s %d words\n", id_str.c_str(), alternative.words_size()) ;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: %d words\n", alternative.words_size()) ;
|
||||
for (int b = 0; b < alternative.words_size(); b++) {
|
||||
auto words = alternative.words(b);
|
||||
cJSON* jWord = cJSON_CreateObject();
|
||||
@@ -487,16 +480,16 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
|
||||
|
||||
if (speech_event_type == StreamingRecognizeResponse_SpeechEventType_END_OF_SINGLE_UTTERANCE) {
|
||||
// we only get this when we have requested it, and recognition stops after we get this
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: %s got end_of_utterance\n", id_str.c_str()) ;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: got end_of_utterance\n") ;
|
||||
cb->got_end_of_utterance = 1;
|
||||
cb->responseHandler(session, "end_of_utterance", cb->bugname);
|
||||
if (cb->wants_single_utterance) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: %s sending writesDone because we want only a single utterance\n", id_str.c_str()) ;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: sending writesDone because we want only a single utterance\n") ;
|
||||
streamer->writesDone();
|
||||
}
|
||||
}
|
||||
switch_core_session_rwunlock(session);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: %s got %d responses\n", response.results_size(), id_str.c_str());
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: got %d responses\n", response.results_size());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -511,11 +504,10 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
|
||||
cb->responseHandler(session, "no_audio", cb->bugname);
|
||||
}
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: %s finish() status %s (%d)\n", id_str.c_str(), status.error_message().c_str(), status.error_code()) ;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "grpc_read_thread: finish() status %s (%d)\n", status.error_message().c_str(), status.error_code()) ;
|
||||
switch_core_session_rwunlock(session);
|
||||
}
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "grpc_read_thread %s exiting\n", id_str.c_str()) ;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -613,9 +605,6 @@ extern "C" {
|
||||
|
||||
if (!cb->vad) streamer->connect();
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s: creating new read thread, streamer %p\n",
|
||||
switch_channel_get_name(channel), (void*)streamer);
|
||||
|
||||
// create the read thread
|
||||
switch_threadattr_t *thd_attr = NULL;
|
||||
switch_memory_pool_t *pool = switch_core_session_get_pool(session);
|
||||
@@ -631,23 +620,17 @@ extern "C" {
|
||||
switch_status_t google_speech_session_cleanup(switch_core_session_t *session, int channelIsClosing, switch_media_bug_t *bug) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s: cleaning up bug %p\n",
|
||||
switch_channel_get_name(channel), (void*)bug);
|
||||
|
||||
if (bug) {
|
||||
struct cap_cb *cb = (struct cap_cb *) switch_core_media_bug_get_user_data(bug);
|
||||
switch_mutex_lock(cb->mutex);
|
||||
|
||||
if (!switch_channel_get_private(channel, cb->bugname)) {
|
||||
// race condition
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s Bug %p is not attached (race).\n",
|
||||
switch_channel_get_name(channel), (void *)bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s Bug is not attached (race).\n", switch_channel_get_name(channel));
|
||||
switch_mutex_unlock(cb->mutex);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
switch_channel_set_private(channel, cb->bugname, NULL);
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "%s set_private set bug to NULL, was %p\n",
|
||||
switch_channel_get_name(channel), (void *)bug);
|
||||
|
||||
// stop playback if available
|
||||
if (cb->play_file == 1){
|
||||
|
||||
@@ -104,10 +104,10 @@ static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data,
|
||||
|
||||
case SWITCH_ABC_TYPE_CLOSE:
|
||||
{
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Got SWITCH_ABC_TYPE_CLOSE, calling google_speech_session_cleanup bug %p.\n", (void *)bug;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Got SWITCH_ABC_TYPE_CLOSE, calling google_speech_session_cleanup.\n");
|
||||
responseHandler(session, "end_of_transcript", cb->bugname);
|
||||
google_speech_session_cleanup(session, 1, bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Finished SWITCH_ABC_TYPE_CLOSE with bug %p.\n", (void *)bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Finished SWITCH_ABC_TYPE_CLOSE.\n");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -143,9 +143,9 @@ static switch_status_t do_stop(switch_core_session_t *session, char *bugname)
|
||||
switch_media_bug_t *bug = switch_channel_get_private(channel, bugname);
|
||||
|
||||
if (bug) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Received user command command, calling google_speech_session_cleanup bug %p\n", (void *)bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Received user command command, calling google_speech_session_cleanup (possibly to stop prev transcribe)\n");
|
||||
status = google_speech_session_cleanup(session, 0, bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "stopped transcription via bug %p.\n", (void *)bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "stopped transcription.\n");
|
||||
}
|
||||
|
||||
return status;
|
||||
@@ -210,10 +210,9 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
||||
const char* model = NULL;
|
||||
const char* var;
|
||||
|
||||
if ((bug = switch_channel_get_private(channel, bugname))) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "removing bug %p from previous transcribe\n", (void *)bug);
|
||||
if (switch_channel_get_private(channel, bugname)) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing bug from previous transcribe\n");
|
||||
do_stop(session, bugname);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "previous bug %p removed, now we can add a new bug\n", (void *)bug);
|
||||
}
|
||||
|
||||
if (switch_true(switch_channel_get_variable(channel, "GOOGLE_SPEECH_SINGLE_UTTERANCE"))) {
|
||||
@@ -277,12 +276,8 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
||||
if ((status = switch_core_media_bug_add(session, bugname, NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "added bug %p\n", (void *)bug);
|
||||
|
||||
//Q: we don't have a lock, so is it possible after creating and before calling set_private
|
||||
// another thread could call do_stop and find no bug to remove?
|
||||
switch_channel_set_private(channel, bugname, bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "called set_private to associate bug %p with channel\n", (void *)bug);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR: %s, response status %d\n", in ? (char *)in : "(null)", rc);
|
||||
if (ap) {
|
||||
ap->m_state = LWS_CLIENT_FAILED;
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECT_FAIL, (char *) in, ap->isFinished(), ap->isInterimTranscriptsEnabled());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECT_FAIL, (char *) in, ap->isFinished(), ap->isInterimTranscriptsEnabled(), ap->getBugname().c_str());
|
||||
}
|
||||
else {
|
||||
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR unable to find wsi %p..\n", wsi);
|
||||
@@ -77,7 +77,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
oss << "}";
|
||||
|
||||
ap->bufferForSending(oss.str().c_str());
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECT_SUCCESS, NULL, ap->isFinished(), ap->isInterimTranscriptsEnabled());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECT_SUCCESS, NULL, ap->isFinished(), ap->isInterimTranscriptsEnabled(), ap->getBugname().c_str());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -97,12 +97,12 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
// closed by us
|
||||
|
||||
lwsl_debug("%s socket closed by us\n", ap->m_uuid.c_str());
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECTION_CLOSED_GRACEFULLY, NULL, ap->isFinished(), ap->isInterimTranscriptsEnabled());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECTION_CLOSED_GRACEFULLY, NULL, ap->isFinished(), ap->isInterimTranscriptsEnabled(), ap->getBugname().c_str());
|
||||
}
|
||||
else if (ap->m_state == LWS_CLIENT_CONNECTED) {
|
||||
// closed by far end
|
||||
lwsl_info("%s socket closed by far end\n", ap->m_uuid.c_str());
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECTION_DROPPED, NULL, ap->isFinished(), ap->isInterimTranscriptsEnabled());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::CONNECTION_DROPPED, NULL, ap->isFinished(), ap->isInterimTranscriptsEnabled(), ap->getBugname().c_str());
|
||||
}
|
||||
ap->m_state = LWS_CLIENT_DISCONNECTED;
|
||||
ap->setClosed();
|
||||
@@ -166,7 +166,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
|
||||
std::string msg((char *)ap->m_recv_buf, ap->m_recv_buf_ptr - ap->m_recv_buf);
|
||||
//std::cerr << "Recv: " << msg << std::endl;
|
||||
|
||||
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::MESSAGE, msg.c_str(), ap->isFinished(), ap->isInterimTranscriptsEnabled());
|
||||
ap->m_callback(ap->m_uuid.c_str(), AudioPipe::MESSAGE, msg.c_str(), ap->isFinished(), ap->isInterimTranscriptsEnabled(), ap->getBugname().c_str());
|
||||
if (nullptr != ap->m_recv_buf) free(ap->m_recv_buf);
|
||||
}
|
||||
ap->m_recv_buf = ap->m_recv_buf_ptr = nullptr;
|
||||
@@ -251,7 +251,6 @@ static const lws_retry_bo_t retry = {
|
||||
};
|
||||
|
||||
struct lws_context *AudioPipe::context = nullptr;
|
||||
std::thread AudioPipe::serviceThread;
|
||||
std::mutex AudioPipe::mutex_connects;
|
||||
std::mutex AudioPipe::mutex_disconnects;
|
||||
std::mutex AudioPipe::mutex_writes;
|
||||
@@ -419,29 +418,26 @@ bool AudioPipe::lws_service_thread() {
|
||||
|
||||
void AudioPipe::initialize(int loglevel, log_emit_function logger) {
|
||||
|
||||
//lws_set_log_level(loglevel, logger);
|
||||
lws_set_log_level(loglevel, logger);
|
||||
|
||||
lwsl_notice("AudioPipe::initialize starting\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
std::thread t(&AudioPipe::lws_service_thread);
|
||||
stopFlag = false;
|
||||
serviceThread = std::thread(&AudioPipe::lws_service_thread);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
bool AudioPipe::deinitialize() {
|
||||
lwsl_notice("AudioPipe::deinitialize\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
stopFlag = true;
|
||||
if (serviceThread.joinable()) {
|
||||
serviceThread.join();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// instance members
|
||||
AudioPipe::AudioPipe(const char* uuid, const char* bugname, const char* host, unsigned int port, const char* path,
|
||||
AudioPipe::AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path,
|
||||
size_t bufLen, size_t minFreespace, notifyHandler_t callback) :
|
||||
m_uuid(uuid), m_host(host), m_port(port), m_path(path), m_finished(false), m_bugname(bugname),
|
||||
m_uuid(uuid), m_host(host), m_port(port), m_path(path), m_finished(false),
|
||||
m_audio_buffer_min_freespace(minFreespace), m_audio_buffer_max_len(bufLen), m_gracefulShutdown(false),
|
||||
m_audio_buffer_write_offset(LWS_PRE), m_recv_buf(nullptr), m_recv_buf_ptr(nullptr), m_interim(false),
|
||||
m_state(LWS_CLIENT_IDLE), m_wsi(nullptr), m_vhd(nullptr), m_callback(callback) {
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
MESSAGE
|
||||
};
|
||||
typedef void (*log_emit_function)(int level, const char *line);
|
||||
typedef void (*notifyHandler_t)(const char *sessionId, const char* bugname, NotifyEvent_t event, const char* message, bool finished, bool wantsInterim);
|
||||
typedef void (*notifyHandler_t)(const char *sessionId, NotifyEvent_t event, const char* message, bool finished, bool wantsInterim, const char* bugname);
|
||||
|
||||
struct lws_per_vhost_data {
|
||||
struct lws_context *context;
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
static bool lws_service_thread();
|
||||
|
||||
// constructor
|
||||
AudioPipe(const char* uuid, const char* bugname, const char* host, unsigned int port, const char* path,
|
||||
AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path,
|
||||
size_t bufLen, size_t minFreespace, notifyHandler_t callback);
|
||||
~AudioPipe();
|
||||
|
||||
@@ -104,7 +104,6 @@ public:
|
||||
void operator=(const AudioPipe&) = delete;
|
||||
|
||||
private:
|
||||
static std::thread serviceThread;
|
||||
|
||||
static int lws_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
static struct lws_context *context;
|
||||
|
||||
@@ -82,8 +82,8 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
static void responseHandler(switch_core_session_t* session, const char* bugname,
|
||||
const char* eventName, const char * json, int finished) {
|
||||
static void responseHandler(switch_core_session_t* session,
|
||||
const char* eventName, const char * json, const char* bugname, int finished) {
|
||||
switch_event_t *event;
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace {
|
||||
return path;
|
||||
}
|
||||
|
||||
static void eventCallback(const char* sessionId, const char* bugname, ibm::AudioPipe::NotifyEvent_t event, const char* message, bool finished, bool wantsInterim) {
|
||||
static void eventCallback(const char* sessionId, ibm::AudioPipe::NotifyEvent_t event, const char* message, bool finished, bool wantsInterim, const char* bugname) {
|
||||
switch_core_session_t* session = switch_core_session_locate(sessionId);
|
||||
if (session) {
|
||||
bool releaseAudioPipe = false;
|
||||
@@ -256,11 +256,10 @@ namespace {
|
||||
tech_pvt->channels = channels;
|
||||
tech_pvt->id = ++idxCallCount;
|
||||
tech_pvt->buffer_overrun_notified = 0;
|
||||
strncpy(tech_pvt->bugname, bugname, MAX_BUG_LEN);
|
||||
|
||||
|
||||
size_t buflen = LWS_PRE + (FRAME_SIZE_8000 * desiredSampling / 8000 * channels * 1000 / RTP_PACKETIZATION_PERIOD * nAudioBufferSecs);
|
||||
|
||||
ibm::AudioPipe* ap = new ibm::AudioPipe(tech_pvt->sessionId, bugname, tech_pvt->host, tech_pvt->port, tech_pvt->path,
|
||||
ibm::AudioPipe* ap = new ibm::AudioPipe(tech_pvt->sessionId, tech_pvt->host, tech_pvt->port, tech_pvt->path,
|
||||
buflen, read_impl.decoded_bytes_per_packet, eventCallback);
|
||||
if (!ap) {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error allocating AudioPipe\n");
|
||||
@@ -312,8 +311,7 @@ extern "C" {
|
||||
switch_status_t ibm_transcribe_init() {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_ibm_transcribe: audio buffer (in secs): %d secs\n", nAudioBufferSecs);
|
||||
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE ;
|
||||
// | LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE || LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
|
||||
ibm::AudioPipe::initialize(logs, lws_logger);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "AudioPipe::initialize completed\n");
|
||||
|
||||
@@ -56,7 +56,7 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
||||
void *pUserData;
|
||||
uint32_t samples_per_second;
|
||||
|
||||
if (switch_channel_get_private(channel, bugname)) {
|
||||
if (switch_channel_get_private(channel, MY_BUG_NAME)) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing bug from previous transcribe\n");
|
||||
do_stop(session, bugname);
|
||||
}
|
||||
@@ -76,7 +76,7 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
||||
if ((status = switch_core_media_bug_add(session, "ibm_transcribe", NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
switch_channel_set_private(channel, bugname, bug);
|
||||
switch_channel_set_private(channel, MY_BUG_NAME, bug);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "added media bug for ibm transcribe\n");
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
@@ -87,7 +87,7 @@ static switch_status_t do_stop(switch_core_session_t *session, char* bugname)
|
||||
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, bugname);
|
||||
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 transcribe.\n");
|
||||
|
||||
@@ -240,7 +240,6 @@ static const lws_retry_bo_t retry = {
|
||||
};
|
||||
|
||||
struct lws_context *AudioPipe::context = nullptr;
|
||||
std::thread AudioPipe::serviceThread;
|
||||
std::mutex AudioPipe::mutex_connects;
|
||||
std::mutex AudioPipe::mutex_disconnects;
|
||||
std::mutex AudioPipe::mutex_writes;
|
||||
@@ -409,21 +408,19 @@ bool AudioPipe::lws_service_thread() {
|
||||
|
||||
void AudioPipe::initialize(int loglevel, log_emit_function logger) {
|
||||
|
||||
//lws_set_log_level(loglevel, logger);
|
||||
lws_set_log_level(loglevel, logger);
|
||||
|
||||
lwsl_notice("AudioPipe::initialize starting\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
std::thread t(&AudioPipe::lws_service_thread);
|
||||
stopFlag = false;
|
||||
serviceThread = std::thread(&AudioPipe::lws_service_thread);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
bool AudioPipe::deinitialize() {
|
||||
lwsl_notice("AudioPipe::deinitialize\n");
|
||||
std::lock_guard<std::mutex> lock(mapMutex);
|
||||
stopFlag = true;
|
||||
if (serviceThread.joinable()) {
|
||||
serviceThread.join();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,6 @@ public:
|
||||
void operator=(const AudioPipe&) = delete;
|
||||
|
||||
private:
|
||||
static std::thread serviceThread;
|
||||
|
||||
static int lws_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
|
||||
static struct lws_context *context;
|
||||
|
||||
@@ -256,6 +256,7 @@ namespace {
|
||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "host: %s, port: %d, path: %s\n", host, port, path);
|
||||
|
||||
strncpy(tech_pvt->sessionId, switch_core_session_get_uuid(session), MAX_SESSION_ID);
|
||||
strncpy(tech_pvt->bugname, bugname, MAX_BUG_LEN);
|
||||
strncpy(tech_pvt->host, host, MAX_WS_URL_LEN);
|
||||
strncpy(tech_pvt->path, path, MAX_PATH_LEN);
|
||||
tech_pvt->port = port;
|
||||
@@ -267,7 +268,6 @@ namespace {
|
||||
tech_pvt->channels = channels;
|
||||
tech_pvt->id = ++idxCallCount;
|
||||
tech_pvt->buffer_overrun_notified = 0;
|
||||
strncpy(tech_pvt->bugname, bugname, MAX_BUG_LEN);
|
||||
|
||||
size_t buflen = LWS_PRE + (FRAME_SIZE_8000 * desiredSampling / 8000 * channels * 1000 / RTP_PACKETIZATION_PERIOD * nAudioBufferSecs);
|
||||
|
||||
@@ -325,8 +325,7 @@ extern "C" {
|
||||
switch_status_t jb_transcribe_init() {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_jambonz_transcribe: audio buffer (in secs): %d secs\n", nAudioBufferSecs);
|
||||
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE ;
|
||||
// | LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
int logs = LLL_ERR | LLL_WARN | LLL_NOTICE || LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY | LLL_DEBUG ;
|
||||
|
||||
jambonz::AudioPipe::initialize(logs, lws_logger);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "AudioPipe::initialize completed\n");
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
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.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
include $(top_srcdir)/build/modmake.rulesam
|
||||
MODNAME=mod_whisper_tts
|
||||
|
||||
mod_LTLIBRARIES = mod_whisper_tts.la
|
||||
mod_whisper_tts_la_SOURCES = mod_whisper_tts.c whisper_glue.cpp
|
||||
mod_whisper_tts_la_CFLAGS = $(AM_CFLAGS)
|
||||
mod_whisper_tts_la_LIBADD = $(switch_builddir)/libfreeswitch.la
|
||||
mod_whisper_tts_la_LDFLAGS = -avoid-version -module -no-undefined -shared `pkg-config --libs boost` -lstdc++ -lmpg123
|
||||
@@ -1,3 +0,0 @@
|
||||
# mod_whisper_tts
|
||||
|
||||
A Freeswitch module that allows speak text to speech audio from whisper stream.
|
||||
@@ -1,164 +0,0 @@
|
||||
#include "mod_whisper_tts.h"
|
||||
#include "whisper_glue.h"
|
||||
|
||||
SWITCH_MODULE_LOAD_FUNCTION(mod_whisper_tts_load);
|
||||
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_whisper_tts_shutdown);
|
||||
SWITCH_MODULE_DEFINITION(mod_whisper_tts, mod_whisper_tts_load, mod_whisper_tts_shutdown, NULL);
|
||||
|
||||
static void clearWhisper(whisper_t* w, int freeAll) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "clearWhisper\n");
|
||||
if (w->api_key) free(w->api_key);
|
||||
if (w->model_id) free(w->model_id);
|
||||
if (w->speed) free(w->speed);
|
||||
if (w->request_id) free(w->request_id);
|
||||
if (w->reported_latency) free(w->reported_latency);
|
||||
if (w->ct) free(w->ct);
|
||||
if (w->err_msg) free(w->err_msg);
|
||||
if (w->name_lookup_time_ms) free(w->name_lookup_time_ms);
|
||||
if (w->connect_time_ms) free(w->connect_time_ms);
|
||||
if (w->final_response_time_ms) free(w->final_response_time_ms);
|
||||
if (w->cache_filename) free(w->cache_filename);
|
||||
|
||||
|
||||
w->api_key = NULL;
|
||||
w->model_id = NULL;
|
||||
w->speed = NULL;
|
||||
w->request_id = NULL;
|
||||
w->reported_latency = NULL;
|
||||
w->ct = NULL;
|
||||
w->err_msg = NULL;
|
||||
w->name_lookup_time_ms = NULL;
|
||||
w->connect_time_ms = NULL;
|
||||
w->final_response_time_ms = NULL;
|
||||
w->cache_filename = NULL;
|
||||
|
||||
if (freeAll) {
|
||||
if (w->voice_name) free(w->voice_name);
|
||||
if (w->session_id) free(w->session_id);
|
||||
w->voice_name = NULL;
|
||||
w->session_id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static whisper_t * createOrRetrievePrivateData(switch_speech_handle_t *sh) {
|
||||
whisper_t *w = (whisper_t *) sh->private_info;
|
||||
if (!w) {
|
||||
w = switch_core_alloc(sh->memory_pool, sizeof(*w));
|
||||
sh->private_info = w;
|
||||
memset(w, 0, sizeof(*w));
|
||||
switch_mutex_init(&w->mutex, SWITCH_MUTEX_NESTED, sh->memory_pool);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "allocated whisper_t\n");
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
switch_status_t w_speech_open(switch_speech_handle_t *sh, const char *voice_name, int rate, int channels, switch_speech_flag_t *flags)
|
||||
{
|
||||
whisper_t *w = createOrRetrievePrivateData(sh);
|
||||
w->voice_name = strdup(voice_name);
|
||||
w->rate = rate;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "w_speech_open voice: %s, rate %d, channels %d\n", voice_name, rate, channels);
|
||||
return whisper_speech_open(w);
|
||||
}
|
||||
|
||||
static switch_status_t w_speech_close(switch_speech_handle_t *sh, switch_speech_flag_t *flags)
|
||||
{
|
||||
switch_status_t rc;
|
||||
whisper_t *w = createOrRetrievePrivateData(sh);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "w_speech_close\n");
|
||||
|
||||
switch_mutex_destroy(w->mutex);
|
||||
|
||||
rc = whisper_speech_close(w);
|
||||
clearWhisper(w, 1);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeswitch will call this function to feed us text to speak
|
||||
*/
|
||||
static switch_status_t w_speech_feed_tts(switch_speech_handle_t *sh, char *text, switch_speech_flag_t *flags)
|
||||
{
|
||||
whisper_t *w = createOrRetrievePrivateData(sh);
|
||||
w->draining = 0;
|
||||
w->reads = 0;
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "w_speech_feed_tts\n");
|
||||
|
||||
return whisper_speech_feed_tts(w, text, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeswitch calls periodically to get some rendered audio in L16 format. We can provide up to 8k of audio at a time.
|
||||
*/
|
||||
static switch_status_t w_speech_read_tts(switch_speech_handle_t *sh, void *data, size_t *datalen, switch_speech_flag_t *flags)
|
||||
{
|
||||
whisper_t *w = createOrRetrievePrivateData(sh);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "w_speech_read_tts\n");
|
||||
return whisper_speech_read_tts(w, data, datalen, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called at the end, not sure exactly what we need to do here..
|
||||
*/
|
||||
static void w_speech_flush_tts(switch_speech_handle_t *sh)
|
||||
{
|
||||
whisper_t *w = createOrRetrievePrivateData(sh);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "w_speech_flush_tts\n");
|
||||
whisper_speech_flush_tts(w);
|
||||
|
||||
clearWhisper(w, 0);
|
||||
}
|
||||
|
||||
static void w_text_param_tts(switch_speech_handle_t *sh, char *param, const char *val)
|
||||
{
|
||||
whisper_t *w = createOrRetrievePrivateData(sh);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "w_text_param_tts: %s=%s\n", param, val);
|
||||
if (0 == strcmp(param, "api_key")) {
|
||||
if (w->api_key) free(w->api_key);
|
||||
w->api_key = strdup(val);
|
||||
} else if (0 == strcmp(param, "voice")) {
|
||||
if (w->voice_name) free(w->voice_name);
|
||||
w->voice_name = strdup(val);
|
||||
} else if (0 == strcmp(param, "model_id")) {
|
||||
if (w->model_id) free(w->model_id);
|
||||
w->model_id = strdup(val);
|
||||
} else if (0 == strcmp(param, "speed")) {
|
||||
if (w->speed) free(w->speed);
|
||||
w->speed = strdup(val);
|
||||
} else if (0 == strcmp(param, "session-uuid")) {
|
||||
if (w->session_id) free(w->session_id);
|
||||
w->session_id = strdup(val);
|
||||
} else if (0 == strcmp(param, "write_cache_file") && switch_true(val)) {
|
||||
w->cache_audio = 1;
|
||||
}
|
||||
}
|
||||
static void w_numeric_param_tts(switch_speech_handle_t *sh, char *param, int val)
|
||||
{
|
||||
}
|
||||
static void w_float_param_tts(switch_speech_handle_t *sh, char *param, double val)
|
||||
{
|
||||
}
|
||||
|
||||
SWITCH_MODULE_LOAD_FUNCTION(mod_whisper_tts_load)
|
||||
{
|
||||
switch_speech_interface_t *speech_interface;
|
||||
|
||||
*module_interface = switch_loadable_module_create_module_interface(pool, modname);
|
||||
speech_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_SPEECH_INTERFACE);
|
||||
speech_interface->interface_name = "whisper";
|
||||
speech_interface->speech_open = w_speech_open;
|
||||
speech_interface->speech_close = w_speech_close;
|
||||
speech_interface->speech_feed_tts = w_speech_feed_tts;
|
||||
speech_interface->speech_read_tts = w_speech_read_tts;
|
||||
speech_interface->speech_flush_tts = w_speech_flush_tts;
|
||||
speech_interface->speech_text_param_tts = w_text_param_tts;
|
||||
speech_interface->speech_numeric_param_tts = w_numeric_param_tts;
|
||||
speech_interface->speech_float_param_tts = w_float_param_tts;
|
||||
return whisper_speech_load();
|
||||
}
|
||||
|
||||
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_whisper_tts_shutdown)
|
||||
{
|
||||
return whisper_speech_unload();
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#ifndef __MOD_WHISPER_TTS_H__
|
||||
#define __MOD_WHISPER_TTS_H__
|
||||
|
||||
#include <switch.h>
|
||||
typedef struct whisper_data {
|
||||
char *voice_name;
|
||||
char *api_key;
|
||||
char *model_id;
|
||||
char *speed;
|
||||
|
||||
/* result data */
|
||||
long response_code;
|
||||
char *ct;
|
||||
char *reported_latency;
|
||||
char *request_id;
|
||||
char *name_lookup_time_ms;
|
||||
char *connect_time_ms;
|
||||
char *final_response_time_ms;
|
||||
char *err_msg;
|
||||
char *cache_filename;
|
||||
char *session_id;
|
||||
|
||||
int rate;
|
||||
int draining;
|
||||
int reads;
|
||||
int cache_audio;
|
||||
|
||||
void *conn;
|
||||
void *circularBuffer;
|
||||
switch_mutex_t *mutex;
|
||||
FILE *file;
|
||||
} whisper_t;
|
||||
#endif
|
||||
@@ -1,952 +0,0 @@
|
||||
#include "mod_whisper_tts.h"
|
||||
#include <switch.h>
|
||||
#include <switch_json.h>
|
||||
#include <curl/curl.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/pool/object_pool.hpp>
|
||||
#include <boost/bind/bind.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "mpg123.h"
|
||||
|
||||
#define BUFFER_GROW_SIZE (8192)
|
||||
#define MP3_DCACHE 8192 * 2
|
||||
|
||||
typedef boost::circular_buffer<uint16_t> CircularBuffer_t;
|
||||
/* Global information, common to all connections */
|
||||
typedef struct
|
||||
{
|
||||
CURLM *multi;
|
||||
int still_running;
|
||||
} GlobalInfo_t;
|
||||
static GlobalInfo_t global;
|
||||
|
||||
/* Information associated with a specific easy handle */
|
||||
typedef struct
|
||||
{
|
||||
CURL *easy;
|
||||
whisper_t* whisper;
|
||||
char* body;
|
||||
struct curl_slist *hdr_list;
|
||||
GlobalInfo_t *global;
|
||||
mpg123_handle *mh;
|
||||
char error[CURL_ERROR_SIZE];
|
||||
FILE* file;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> startTime;
|
||||
bool flushed;
|
||||
} ConnInfo_t;
|
||||
|
||||
|
||||
static boost::object_pool<ConnInfo_t> pool ;
|
||||
static std::map<curl_socket_t, boost::asio::ip::tcp::socket *> socket_map;
|
||||
static boost::asio::io_service io_service;
|
||||
static boost::asio::deadline_timer timer(io_service);
|
||||
static std::string fullDirPath;
|
||||
static std::thread worker_thread;
|
||||
|
||||
std::string secondsToMillisecondsString(double seconds) {
|
||||
// Convert to milliseconds
|
||||
double milliseconds = seconds * 1000.0;
|
||||
|
||||
// Truncate to remove fractional part
|
||||
long milliseconds_long = static_cast<long>(milliseconds);
|
||||
|
||||
// Convert to string
|
||||
return std::to_string(milliseconds_long);
|
||||
}
|
||||
|
||||
static CURL* createEasyHandle(void) {
|
||||
CURL* easy = curl_easy_init();
|
||||
if(!easy) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "curl_easy_init() failed!\n");
|
||||
return nullptr ;
|
||||
}
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(easy, CURLOPT_USERAGENT, "jambonz/0.8.5");
|
||||
|
||||
// set connect timeout to 3 seconds and total timeout to 109 seconds
|
||||
curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT_MS, 3000L);
|
||||
curl_easy_setopt(easy, CURLOPT_TIMEOUT, 10L);
|
||||
|
||||
return easy ;
|
||||
}
|
||||
|
||||
static void cleanupConn(ConnInfo_t *conn) {
|
||||
auto w = conn->whisper;
|
||||
|
||||
if (conn->mh) {
|
||||
mpg123_close(conn->mh);
|
||||
mpg123_delete(conn->mh);
|
||||
}
|
||||
|
||||
if( conn->hdr_list ) {
|
||||
curl_slist_free_all(conn->hdr_list);
|
||||
conn->hdr_list = nullptr ;
|
||||
}
|
||||
curl_easy_cleanup(conn->easy);
|
||||
|
||||
if (conn->file) {
|
||||
if (fclose(conn->file) != 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cleanupConn: error closing audio cache file\n");
|
||||
}
|
||||
conn->file = nullptr ;
|
||||
}
|
||||
|
||||
w->conn = nullptr ;
|
||||
w->draining = 1;
|
||||
|
||||
memset(conn, 0, sizeof(ConnInfo_t));
|
||||
pool.destroy(conn) ;
|
||||
}
|
||||
|
||||
/* Check for completed transfers, and remove their easy handles */
|
||||
void check_multi_info(GlobalInfo_t *g) {
|
||||
CURLMsg *msg;
|
||||
int msgs_left;
|
||||
ConnInfo_t *conn;
|
||||
CURL *easy;
|
||||
CURLcode res;
|
||||
|
||||
while((msg = curl_multi_info_read(g->multi, &msgs_left))) {
|
||||
if(msg->msg == CURLMSG_DONE) {
|
||||
long response_code;
|
||||
double namelookup=0, connect=0, total=0 ;
|
||||
char *ct = NULL ;
|
||||
|
||||
easy = msg->easy_handle;
|
||||
res = msg->data.result;
|
||||
curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
|
||||
curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
curl_easy_getinfo(easy, CURLINFO_CONTENT_TYPE, &ct);
|
||||
|
||||
curl_easy_getinfo(easy, CURLINFO_NAMELOOKUP_TIME, &namelookup);
|
||||
curl_easy_getinfo(easy, CURLINFO_CONNECT_TIME, &connect);
|
||||
curl_easy_getinfo(easy, CURLINFO_TOTAL_TIME, &total);
|
||||
|
||||
auto w = conn->whisper;
|
||||
w->response_code = response_code;
|
||||
if (ct) w->ct = strdup(ct);
|
||||
|
||||
std::string name_lookup_ms = secondsToMillisecondsString(namelookup);
|
||||
std::string connect_ms = secondsToMillisecondsString(connect);
|
||||
std::string final_response_time_ms = secondsToMillisecondsString(total);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
|
||||
"mod_whisper_tts: response: %ld, content-type %s,"
|
||||
"dns(ms): %" CURL_FORMAT_CURL_OFF_T ".%06ld, "
|
||||
"connect(ms): %" CURL_FORMAT_CURL_OFF_T ".%06ld, "
|
||||
"total(ms): %" CURL_FORMAT_CURL_OFF_T ".%06ld\n",
|
||||
response_code, ct,
|
||||
(long)(namelookup), (long)(fmod(namelookup, 1.0) * 1000000),
|
||||
(long)(connect), (long)(fmod(connect, 1.0) * 1000000),
|
||||
(long)(total), (long)(fmod(total, 1.0) * 1000000));
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "name lookup time: %s\n", name_lookup_ms.c_str());
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "connect time: %s\n", connect_ms.c_str());
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "final response time: %s\n", final_response_time_ms.c_str());
|
||||
|
||||
w->name_lookup_time_ms = strdup(name_lookup_ms.c_str());
|
||||
w->connect_time_ms = strdup(connect_ms.c_str());
|
||||
w->final_response_time_ms = strdup(final_response_time_ms.c_str());
|
||||
|
||||
curl_multi_remove_handle(g->multi, easy);
|
||||
cleanupConn(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int mcode_test(const char *where, CURLMcode code) {
|
||||
if(CURLM_OK != code) {
|
||||
const char *s;
|
||||
switch(code) {
|
||||
case CURLM_CALL_MULTI_PERFORM:
|
||||
s = "CURLM_CALL_MULTI_PERFORM";
|
||||
break;
|
||||
case CURLM_BAD_HANDLE:
|
||||
s = "CURLM_BAD_HANDLE";
|
||||
break;
|
||||
case CURLM_BAD_EASY_HANDLE:
|
||||
s = "CURLM_BAD_EASY_HANDLE";
|
||||
break;
|
||||
case CURLM_OUT_OF_MEMORY:
|
||||
s = "CURLM_OUT_OF_MEMORY";
|
||||
break;
|
||||
case CURLM_INTERNAL_ERROR:
|
||||
s = "CURLM_INTERNAL_ERROR";
|
||||
break;
|
||||
case CURLM_UNKNOWN_OPTION:
|
||||
s = "CURLM_UNKNOWN_OPTION";
|
||||
break;
|
||||
case CURLM_LAST:
|
||||
s = "CURLM_LAST";
|
||||
break;
|
||||
default:
|
||||
s = "CURLM_unknown";
|
||||
break;
|
||||
case CURLM_BAD_SOCKET:
|
||||
s = "CURLM_BAD_SOCKET";
|
||||
break;
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mcode_test ERROR: %s returns %s:%d\n", where, s, code);
|
||||
|
||||
return -1;
|
||||
}
|
||||
return 0 ;
|
||||
}
|
||||
|
||||
static void remsock(int *f, GlobalInfo_t *g) {
|
||||
if(f) {
|
||||
free(f);
|
||||
f = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Called by asio when there is an action on a socket */
|
||||
static void event_cb(GlobalInfo_t *g, curl_socket_t s, int action, const boost::system::error_code & error, int *fdp) {
|
||||
int f = *fdp;
|
||||
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "event_cb socket %#X has action %d\n", s, action) ;
|
||||
|
||||
// Socket already POOL REMOVED.
|
||||
if (f == CURL_POLL_REMOVE) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "event_cb socket %#X removed\n", s);
|
||||
remsock(fdp, g);
|
||||
return;
|
||||
}
|
||||
|
||||
if(socket_map.find(s) == socket_map.end()) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "event_cb: socket %#X already closed\n, s");
|
||||
return;
|
||||
}
|
||||
|
||||
/* make sure the event matches what are wanted */
|
||||
if(f == action || f == CURL_POLL_INOUT) {
|
||||
if(error) {
|
||||
action = CURL_CSELECT_ERR;
|
||||
}
|
||||
CURLMcode rc = curl_multi_socket_action(g->multi, s, action, &g->still_running);
|
||||
|
||||
mcode_test("event_cb: curl_multi_socket_action", rc);
|
||||
check_multi_info(g);
|
||||
|
||||
if(g->still_running <= 0) {
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
/* keep on watching.
|
||||
* the socket may have been closed and/or fdp may have been changed
|
||||
* in curl_multi_socket_action(), so check them both */
|
||||
if(!error && socket_map.find(s) != socket_map.end() &&
|
||||
(f == action || f == CURL_POLL_INOUT)) {
|
||||
boost::asio::ip::tcp::socket *tcp_socket = socket_map.find(s)->second;
|
||||
|
||||
if(action == CURL_POLL_IN) {
|
||||
tcp_socket->async_read_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
action, boost::placeholders::_1, fdp));
|
||||
}
|
||||
if(action == CURL_POLL_OUT) {
|
||||
tcp_socket->async_write_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
action, boost::placeholders::_1, fdp));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* socket functions */
|
||||
static void setsock(int *fdp, curl_socket_t s, CURL *e, int act, int oldact, GlobalInfo_t *g) {
|
||||
std::map<curl_socket_t, boost::asio::ip::tcp::socket *>::iterator it = socket_map.find(s);
|
||||
|
||||
if(it == socket_map.end()) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "setsock: socket %#X not found\n, s");
|
||||
return;
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::socket * tcp_socket = it->second;
|
||||
|
||||
*fdp = act;
|
||||
|
||||
if(act == CURL_POLL_IN) {
|
||||
if(oldact != CURL_POLL_IN && oldact != CURL_POLL_INOUT) {
|
||||
tcp_socket->async_read_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
CURL_POLL_IN, boost::placeholders::_1, fdp));
|
||||
}
|
||||
}
|
||||
else if(act == CURL_POLL_OUT) {
|
||||
if(oldact != CURL_POLL_OUT && oldact != CURL_POLL_INOUT) {
|
||||
tcp_socket->async_write_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
CURL_POLL_OUT, boost::placeholders::_1, fdp));
|
||||
}
|
||||
}
|
||||
else if(act == CURL_POLL_INOUT) {
|
||||
if(oldact != CURL_POLL_IN && oldact != CURL_POLL_INOUT) {
|
||||
tcp_socket->async_read_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
CURL_POLL_IN, boost::placeholders::_1, fdp));
|
||||
}
|
||||
if(oldact != CURL_POLL_OUT && oldact != CURL_POLL_INOUT) {
|
||||
tcp_socket->async_write_some(boost::asio::null_buffers(),
|
||||
boost::bind(&event_cb, g, s,
|
||||
CURL_POLL_OUT, boost::placeholders::_1, fdp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo_t *g) {
|
||||
/* fdp is used to store current action */
|
||||
int *fdp = (int *) calloc(sizeof(int), 1);
|
||||
|
||||
setsock(fdp, s, easy, action, 0, g);
|
||||
curl_multi_assign(g->multi, s, fdp);
|
||||
}
|
||||
|
||||
static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) {
|
||||
GlobalInfo_t *g = &global;
|
||||
|
||||
int *actionp = (int *) sockp;
|
||||
static const char *whatstr[] = { "none", "IN", "OUT", "INOUT", "REMOVE"};
|
||||
|
||||
if(what == CURL_POLL_REMOVE) {
|
||||
*actionp = what;
|
||||
}
|
||||
else {
|
||||
if(!actionp) {
|
||||
addsock(s, e, what, g);
|
||||
}
|
||||
else {
|
||||
setsock(actionp, s, e, what, *actionp, g);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void threadFunc() {
|
||||
/* to make sure the event loop doesn't terminate when there is no work to do */
|
||||
io_service.reset() ;
|
||||
boost::asio::io_service::work work(io_service);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_whisper_tts threadFunc - starting\n");
|
||||
|
||||
for(;;) {
|
||||
|
||||
try {
|
||||
io_service.run() ;
|
||||
break ;
|
||||
}
|
||||
catch( std::exception& e) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mod_whisper_tts threadFunc - Error: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "mod_whisper_tts threadFunc - ending\n");
|
||||
}
|
||||
|
||||
|
||||
/* Called by asio when our timeout expires */
|
||||
static void timer_cb(const boost::system::error_code & error, GlobalInfo_t *g)
|
||||
{
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "timer_cb\n");
|
||||
|
||||
if(!error) {
|
||||
CURLMcode rc = curl_multi_socket_action(g->multi, CURL_SOCKET_TIMEOUT, 0, &g->still_running);
|
||||
mcode_test("timer_cb: curl_multi_socket_action", rc);
|
||||
check_multi_info(g);
|
||||
}
|
||||
}
|
||||
|
||||
int multi_timer_cb(CURLM *multi, long timeout_ms, GlobalInfo_t *g) {
|
||||
|
||||
/* cancel running timer */
|
||||
timer.cancel();
|
||||
|
||||
if(timeout_ms >= 0) {
|
||||
// from libcurl 7.88.1-10+deb12u4 does not allow call curl_multi_socket_action or curl_multi_perform in curl_multi callback directly
|
||||
timer.expires_from_now(boost::posix_time::millisec(timeout_ms ? timeout_ms : 1));
|
||||
timer.async_wait(boost::bind(&timer_cb, boost::placeholders::_1, g));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::vector<uint16_t> convert_mp3_to_linear(ConnInfo_t *conn, uint8_t *data, size_t len) {
|
||||
std::vector<uint16_t> linear_data;
|
||||
int eof = 0;
|
||||
int mp3err = 0;
|
||||
unsigned char decode_buf[MP3_DCACHE];
|
||||
|
||||
if(mpg123_feed(conn->mh, data, len) == MPG123_OK) {
|
||||
while(!eof) {
|
||||
size_t usedlen = 0;
|
||||
off_t frame_offset;
|
||||
unsigned char* audio;
|
||||
|
||||
int decode_status = mpg123_decode_frame(conn->mh, &frame_offset, &audio, &usedlen);
|
||||
|
||||
switch(decode_status) {
|
||||
case MPG123_NEW_FORMAT:
|
||||
continue;
|
||||
|
||||
case MPG123_OK:
|
||||
for(size_t i = 0; i < usedlen; i += 2) {
|
||||
uint16_t value = reinterpret_cast<uint16_t*>(audio)[i / 2];
|
||||
linear_data.push_back(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case MPG123_DONE:
|
||||
case MPG123_NEED_MORE:
|
||||
eof = 1;
|
||||
break;
|
||||
|
||||
case MPG123_ERR:
|
||||
default:
|
||||
if(++mp3err >= 5) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Decoder Error!\n");
|
||||
eof = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (eof)
|
||||
break;
|
||||
|
||||
mp3err = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return linear_data;
|
||||
}
|
||||
/* CURLOPT_WRITEFUNCTION */
|
||||
static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
||||
bool fireEvent = false;
|
||||
uint8_t *data = (uint8_t *) ptr;
|
||||
size_t bytes_received = size * nmemb;
|
||||
auto w = conn->whisper;
|
||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) w->circularBuffer;
|
||||
std::vector<uint16_t> pcm_data;
|
||||
|
||||
if (conn->flushed) {
|
||||
/* this will abort the transfer */
|
||||
return 0;
|
||||
}
|
||||
{
|
||||
switch_mutex_lock(w->mutex);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "write_cb: received data, response %ld\n",
|
||||
w->response_code);
|
||||
|
||||
if (w->response_code > 0 && w->response_code != 200) {
|
||||
std::string body((char *) ptr, bytes_received);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: received body %s\n", body.c_str());
|
||||
w->err_msg = strdup(body.c_str());
|
||||
switch_mutex_unlock(w->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cache file will stay in the mp3 format for size (smaller) and simplicity */
|
||||
if (conn->file) fwrite(data, sizeof(uint8_t), bytes_received, conn->file);
|
||||
|
||||
pcm_data = convert_mp3_to_linear(conn, data, bytes_received);
|
||||
size_t bytesResampled = pcm_data.size() * sizeof(uint16_t);
|
||||
|
||||
// Resize the buffer if necessary
|
||||
if (cBuffer->capacity() - cBuffer->size() < (bytesResampled / sizeof(uint16_t))) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "write_cb growing buffer\n");
|
||||
|
||||
//TODO: if buffer exceeds some max size, return CURL_WRITEFUNC_ERROR to abort the transfer
|
||||
cBuffer->set_capacity(cBuffer->size() + std::max((bytesResampled / sizeof(uint16_t)), (size_t)BUFFER_GROW_SIZE));
|
||||
}
|
||||
|
||||
/* Push the data into the buffer */
|
||||
cBuffer->insert(cBuffer->end(), pcm_data.data(), pcm_data.data() + pcm_data.size());
|
||||
|
||||
if (0 == w->reads++) {
|
||||
fireEvent = true;
|
||||
}
|
||||
switch_mutex_unlock(w->mutex);
|
||||
}
|
||||
if (fireEvent && w->session_id) {
|
||||
auto endTime = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - conn->startTime);
|
||||
auto time_to_first_byte_ms = std::to_string(duration.count());
|
||||
switch_core_session_t* session = switch_core_session_locate(w->session_id);
|
||||
if (session) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
if (channel) {
|
||||
switch_event_t *event;
|
||||
if (switch_event_create(&event, SWITCH_EVENT_PLAYBACK_START) == SWITCH_STATUS_SUCCESS) {
|
||||
switch_channel_event_set_data(channel, event);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "write_cb: firing playback-started\n");
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Playback-File-Type", "tts_stream");
|
||||
if (w->reported_latency) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_whisper_reported_latency_ms", w->reported_latency);
|
||||
}
|
||||
if (w->request_id) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_whisper_request_id", w->request_id);
|
||||
}
|
||||
if (w->name_lookup_time_ms) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_whisper_name_lookup_time_ms", w->name_lookup_time_ms);
|
||||
}
|
||||
if (w->connect_time_ms) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_whisper_connect_time_ms", w->connect_time_ms);
|
||||
}
|
||||
if (w->final_response_time_ms) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_whisper_final_response_time_ms", w->final_response_time_ms);
|
||||
}
|
||||
if (w->voice_name) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_whisper_voice_name", w->voice_name);
|
||||
}
|
||||
if (w->model_id) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_whisper_model_id", w->model_id);
|
||||
}
|
||||
if (w->cache_filename) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", w->cache_filename);
|
||||
}
|
||||
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_time_to_first_byte_ms", time_to_first_byte_ms.c_str());
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: failed to create event\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: channel not found\n");
|
||||
}
|
||||
switch_core_session_rwunlock(session);
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: session %s not found\n", w->session_id);
|
||||
}
|
||||
}
|
||||
return bytes_received;
|
||||
}
|
||||
|
||||
static bool parseHeader(const std::string& str, std::string& header, std::string& value) {
|
||||
std::vector<std::string> parts;
|
||||
boost::split(parts, str, boost::is_any_of(":"), boost::token_compress_on);
|
||||
|
||||
if (parts.size() != 2)
|
||||
return false;
|
||||
|
||||
header = boost::trim_copy(parts[0]);
|
||||
value = boost::trim_copy(parts[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t header_callback(char *buffer, size_t size, size_t nitems, ConnInfo_t *conn) {
|
||||
size_t bytes_received = size * nitems;
|
||||
const std::string prefix = "HTTP/2 ";
|
||||
whisper_t* w = conn->whisper;
|
||||
std::string header, value;
|
||||
std::string input(buffer, bytes_received);
|
||||
if (parseHeader(input, header, value)) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "recv header: %s with value %s\n", header.c_str(), value.c_str());
|
||||
if (0 == header.compare("openai-processing-ms")) w->reported_latency = strdup(value.c_str());
|
||||
else if (0 == header.compare("x-request-id")) w->request_id = strdup(value.c_str());
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "recv header: %s\n", input.c_str());
|
||||
if (input.rfind(prefix, 0) == 0) {
|
||||
try {
|
||||
w->response_code = std::stoi(input.substr(prefix.length()));
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "parsed response code: %ld\n", w->response_code);
|
||||
} catch (const std::invalid_argument& e) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "header_callback: invalid response code %s\n", input.substr(prefix.length()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytes_received;
|
||||
}
|
||||
|
||||
/* CURLOPT_OPENSOCKETFUNCTION */
|
||||
static curl_socket_t opensocket(void *clientp, curlsocktype purpose, struct curl_sockaddr *address) {
|
||||
curl_socket_t sockfd = CURL_SOCKET_BAD;
|
||||
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "opensocket: %d\n", purpose);
|
||||
/* restrict to IPv4 */
|
||||
if(purpose == CURLSOCKTYPE_IPCXN && address->family == AF_INET) {
|
||||
/* create a tcp socket object */
|
||||
boost::asio::ip::tcp::socket *tcp_socket = new boost::asio::ip::tcp::socket(io_service);
|
||||
|
||||
/* open it and get the native handle*/
|
||||
boost::system::error_code ec;
|
||||
tcp_socket->open(boost::asio::ip::tcp::v4(), ec);
|
||||
|
||||
if(ec) {
|
||||
/* An error occurred */
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't open socket [%ld][%s]\n", ec, ec.message().c_str());
|
||||
}
|
||||
else {
|
||||
sockfd = tcp_socket->native_handle();
|
||||
|
||||
/* save it for monitoring */
|
||||
socket_map.insert(std::pair<curl_socket_t, boost::asio::ip::tcp::socket *>(sockfd, tcp_socket));
|
||||
}
|
||||
}
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/* CURLOPT_CLOSESOCKETFUNCTION */
|
||||
static int close_socket(void *clientp, curl_socket_t item) {
|
||||
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "close_socket : %#X\n", item);
|
||||
|
||||
std::map<curl_socket_t, boost::asio::ip::tcp::socket *>::iterator it = socket_map.find(item);
|
||||
if(it != socket_map.end()) {
|
||||
delete it->second;
|
||||
socket_map.erase(it);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
extern "C" {
|
||||
switch_status_t whisper_speech_load() {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "whisper_speech_loading..\n");
|
||||
memset(&global, 0, sizeof(GlobalInfo_t));
|
||||
global.multi = curl_multi_init();
|
||||
|
||||
if (!global.multi) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "whisper_speech_load curl_multi_init() failed, exiting!\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
curl_multi_setopt(global.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
|
||||
curl_multi_setopt(global.multi, CURLMOPT_SOCKETDATA, &global);
|
||||
curl_multi_setopt(global.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
|
||||
curl_multi_setopt(global.multi, CURLMOPT_TIMERDATA, &global);
|
||||
curl_multi_setopt(global.multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
||||
|
||||
/* create temp folder for cache files */
|
||||
const char* baseDir = std::getenv("JAMBONZ_TMP_CACHE_FOLDER");
|
||||
if (!baseDir) {
|
||||
baseDir = "/var/";
|
||||
}
|
||||
if (strcmp(baseDir, "/") == 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to create folder %s\n", baseDir);
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
fullDirPath = std::string(baseDir) + "jambonz-tts-cache-files";
|
||||
|
||||
// Create the directory with read, write, and execute permissions for everyone
|
||||
mode_t oldMask = umask(0);
|
||||
int result = mkdir(fullDirPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
|
||||
umask(oldMask);
|
||||
if (result != 0) {
|
||||
if (errno != EEXIST) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to create folder %s\n", fullDirPath.c_str());
|
||||
fullDirPath = "";
|
||||
}
|
||||
else switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "folder %s already exists\n", fullDirPath.c_str());
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "created folder %s\n", fullDirPath.c_str());
|
||||
}
|
||||
// init mgp123
|
||||
if (mpg123_init() != MPG123_OK) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "failed to initiate MPG123");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
/* start worker thread that handles transfers*/
|
||||
std::thread t(threadFunc) ;
|
||||
worker_thread.swap( t ) ;
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "whisper_speech_loaded..\n");
|
||||
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t whisper_speech_unload() {
|
||||
/* stop the ASIO IO service */
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "whisper_speech_unload: stopping io service\n");
|
||||
io_service.stop();
|
||||
|
||||
/* Join the worker thread */
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "whisper_speech_unload: wait for worker thread to complete\n");
|
||||
if (worker_thread.joinable()) {
|
||||
worker_thread.join();
|
||||
}
|
||||
|
||||
/* cleanup curl multi handle*/
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "whisper_speech_unload: release curl multi\n");
|
||||
curl_multi_cleanup(global.multi);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "whisper_speech_unload: completed\n");
|
||||
|
||||
mpg123_exit();
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t whisper_speech_open(whisper_t* whisper) {
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t whisper_speech_feed_tts(whisper_t* w, char* text, switch_speech_flag_t *flags) {
|
||||
CURLMcode rc;
|
||||
|
||||
const int MAX_CHARS = 20;
|
||||
char tempText[MAX_CHARS + 4]; // +4 for the ellipsis and null terminator
|
||||
|
||||
if (strlen(text) > MAX_CHARS) {
|
||||
strncpy(tempText, text, MAX_CHARS);
|
||||
strcpy(tempText + MAX_CHARS, "...");
|
||||
} else {
|
||||
strcpy(tempText, text);
|
||||
}
|
||||
|
||||
/* open cache file */
|
||||
if (w->cache_audio && fullDirPath.length() > 0) {
|
||||
switch_uuid_t uuid;
|
||||
char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1];
|
||||
char outfile[512] = "";
|
||||
int fd;
|
||||
|
||||
switch_uuid_get(&uuid);
|
||||
switch_uuid_format(uuid_str, &uuid);
|
||||
|
||||
switch_snprintf(outfile, sizeof(outfile), "%s%s%s.mp3", fullDirPath.c_str(), SWITCH_PATH_SEPARATOR, uuid_str);
|
||||
w->cache_filename = strdup(outfile);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "writing audio cache file to %s\n", w->cache_filename);
|
||||
|
||||
mode_t oldMask = umask(0);
|
||||
fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
umask(oldMask);
|
||||
if (fd == -1 ) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening cache file %s: %s\n", outfile, strerror(errno));
|
||||
}
|
||||
else {
|
||||
w->file = fdopen(fd, "wb");
|
||||
if (!w->file) {
|
||||
close(fd);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening cache file %s: %s\n", outfile, strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!w->api_key) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "whisper_speech_feed_tts: no api_key provided\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
if (!w->model_id) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "whisper_speech_feed_tts: no model_id provided\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
/* format url*/
|
||||
std::string url = "https://api.openai.com/v1/audio/speech";
|
||||
|
||||
/* create the JSON body */
|
||||
cJSON * jResult = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(jResult, "model", w->model_id);
|
||||
cJSON_AddStringToObject(jResult, "input", text);
|
||||
cJSON_AddStringToObject(jResult, "voice", w->voice_name);
|
||||
cJSON_AddStringToObject(jResult, "response_format", "mp3");
|
||||
if (w->speed) {
|
||||
cJSON_AddStringToObject(jResult, "speed", w->speed);
|
||||
}
|
||||
char *json = cJSON_PrintUnformatted(jResult);;
|
||||
|
||||
cJSON_Delete(jResult);
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "whisper_speech_feed_tts: [%s] [%s]\n", url.c_str(), tempText);
|
||||
|
||||
ConnInfo_t *conn = pool.malloc() ;
|
||||
|
||||
// COnfigure MPG123
|
||||
int mhError = 0;
|
||||
mpg123_handle *mh = mpg123_new("auto", &mhError);
|
||||
if (!mh) {
|
||||
const char *mhErr = mpg123_plain_strerror(mhError);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error allocating mpg123 handle! %s\n", switch_str_nil(mhErr));
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (mpg123_open_feed(mh) != MPG123_OK) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error mpg123_open_feed!\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (mpg123_format_all(mh) != MPG123_OK) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error mpg123_format_all!\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
if (mpg123_param(mh, MPG123_FORCE_RATE, 8000 /*Hz*/, 0) != MPG123_OK) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error mpg123_param!\n");
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
|
||||
CURL* easy = createEasyHandle();
|
||||
w->conn = (void *) conn ;
|
||||
conn->whisper = w;
|
||||
conn->easy = easy;
|
||||
conn->mh = mh;
|
||||
conn->global = &global;
|
||||
conn->hdr_list = NULL ;
|
||||
conn->file = w->file;
|
||||
conn->body = json;
|
||||
conn->flushed = false;
|
||||
|
||||
w->circularBuffer = (void *) new CircularBuffer_t(8192);
|
||||
|
||||
std::ostringstream api_key_stream;
|
||||
api_key_stream << "Authorization: Bearer " << w->api_key;
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(easy, CURLOPT_WRITEDATA, conn);
|
||||
curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, conn->error);
|
||||
curl_easy_setopt(easy, CURLOPT_PRIVATE, conn);
|
||||
curl_easy_setopt(easy, CURLOPT_VERBOSE, 0L);
|
||||
curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1L);
|
||||
curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, header_callback);
|
||||
curl_easy_setopt(easy, CURLOPT_HEADERDATA, conn);
|
||||
|
||||
/* call this function to get a socket */
|
||||
curl_easy_setopt(easy, CURLOPT_OPENSOCKETFUNCTION, opensocket);
|
||||
|
||||
/* call this function to close a socket */
|
||||
curl_easy_setopt(easy, CURLOPT_CLOSESOCKETFUNCTION, close_socket);
|
||||
|
||||
conn->hdr_list = curl_slist_append(conn->hdr_list, api_key_stream.str().c_str());
|
||||
conn->hdr_list = curl_slist_append(conn->hdr_list, "Content-Type: application/json");
|
||||
curl_easy_setopt(easy, CURLOPT_HTTPHEADER, conn->hdr_list);
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
||||
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
||||
|
||||
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||
|
||||
rc = curl_multi_add_handle(global.multi, conn->easy);
|
||||
mcode_test("new_conn: curl_multi_add_handle", rc);
|
||||
|
||||
/* start a timer to measure the duration until we receive first byte of audio */
|
||||
conn->startTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "whisper_speech_feed_tts: called curl_multi_add_handle\n");
|
||||
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t whisper_speech_read_tts(whisper_t* w, void *data, size_t *datalen, switch_speech_flag_t *flags) {
|
||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) w->circularBuffer;
|
||||
std::vector<uint16_t> pcm_data;
|
||||
|
||||
{
|
||||
switch_mutex_lock(w->mutex);
|
||||
ConnInfo_t *conn = (ConnInfo_t *) w->conn;
|
||||
|
||||
if (w->response_code > 0 && w->response_code != 200) {
|
||||
switch_mutex_unlock(w->mutex);
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "whisper_speech_read_tts, returning failure\n") ;
|
||||
return SWITCH_STATUS_FALSE;
|
||||
}
|
||||
if (conn && conn->flushed) {
|
||||
switch_mutex_unlock(w->mutex);
|
||||
return SWITCH_STATUS_BREAK;
|
||||
}
|
||||
if (cBuffer->empty()) {
|
||||
if (w->draining) {
|
||||
switch_mutex_unlock(w->mutex);
|
||||
return SWITCH_STATUS_BREAK;
|
||||
}
|
||||
/* no audio available yet so send silence */
|
||||
memset(data, 255, *datalen);
|
||||
switch_mutex_unlock(w->mutex);
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
size_t size = std::min((*datalen/2), cBuffer->size());
|
||||
pcm_data.insert(pcm_data.end(), cBuffer->begin(), cBuffer->begin() + size);
|
||||
cBuffer->erase(cBuffer->begin(), cBuffer->begin() + size);
|
||||
switch_mutex_unlock(w->mutex);
|
||||
}
|
||||
|
||||
memcpy(data, pcm_data.data(), pcm_data.size() * sizeof(uint16_t));
|
||||
*datalen = pcm_data.size() * sizeof(uint16_t);
|
||||
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t whisper_speech_flush_tts(whisper_t* w) {
|
||||
bool download_complete = w->response_code == 200;
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "whisper_speech_flush_tts, download complete? %s\n", download_complete ? "yes" : "no") ;
|
||||
|
||||
ConnInfo_t *conn = (ConnInfo_t *) w->conn;
|
||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) w->circularBuffer;
|
||||
delete cBuffer;
|
||||
w->circularBuffer = nullptr ;
|
||||
|
||||
if (conn) {
|
||||
conn->flushed = true;
|
||||
if (!download_complete) {
|
||||
if (conn->file) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "closing audio cache file %s because download was interrupted\n", w->cache_filename);
|
||||
if (fclose(conn->file) != 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "error closing audio cache file\n");
|
||||
}
|
||||
conn->file = nullptr ;
|
||||
}
|
||||
|
||||
if (w->cache_filename) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing audio cache file %s because download was interrupted\n", w->cache_filename);
|
||||
if (unlink(w->cache_filename) != 0) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cleanupConn: error removing audio cache file %s: %d:%s\n",
|
||||
w->cache_filename, errno, strerror(errno));
|
||||
}
|
||||
free(w->cache_filename);
|
||||
w->cache_filename = nullptr ;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (w->session_id) {
|
||||
switch_core_session_t* session = switch_core_session_locate(w->session_id);
|
||||
if (session) {
|
||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||
if (channel) {
|
||||
switch_event_t *event;
|
||||
if (switch_event_create(&event, SWITCH_EVENT_PLAYBACK_STOP) == SWITCH_STATUS_SUCCESS) {
|
||||
switch_channel_event_set_data(channel, event);
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Playback-File-Type", "tts_stream");
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_whisper_response_code", std::to_string(w->response_code).c_str());
|
||||
if (w->cache_filename && w->response_code == 200) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", w->cache_filename);
|
||||
}
|
||||
if (w->response_code != 200 && w->err_msg) {
|
||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_error", w->err_msg);
|
||||
}
|
||||
switch_event_fire(&event);
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: failed to create event\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: channel not found\n");
|
||||
}
|
||||
switch_core_session_rwunlock(session);
|
||||
}
|
||||
}
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
switch_status_t whisper_speech_close(whisper_t* w) {
|
||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "whisper_speech_close\n") ;
|
||||
return SWITCH_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#ifndef __WHISPER_GLUE_H__
|
||||
#define __WHISPER_GLUE_H__
|
||||
|
||||
switch_status_t whisper_speech_load();
|
||||
switch_status_t whisper_speech_open(whisper_t* whisper);
|
||||
switch_status_t whisper_speech_feed_tts(whisper_t* whisper, char* text, switch_speech_flag_t *flags);
|
||||
switch_status_t whisper_speech_read_tts(whisper_t* whisper, void *data, size_t *datalen, switch_speech_flag_t *flags);
|
||||
switch_status_t whisper_speech_flush_tts(whisper_t* whisper);
|
||||
switch_status_t whisper_speech_close(whisper_t* whisper);
|
||||
switch_status_t whisper_speech_unload();
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user