Compare commits

...

6 Commits

Author SHA1 Message Date
Hoan Luu Huu 63e66042af fix cannot build mod_audio_fork (#29)
Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>
2024-03-31 21:08:01 -04:00
Hoan Luu Huu eedcc03112 support streaming audio for mod_audio_fork (#28)
* support streaming audio for mod_audio_fork

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* wip

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>

* fix review comments

* fix review comments

---------

Signed-off-by: Hoan HL <quan.luuhoang8@gmail.com>
2024-03-31 09:05:50 -04:00
Dave Horton 07dddd0094 fix deadlock bug 2024-03-29 19:55:16 -04:00
Andrew Golledge 14297ce3a6 Handle grpc_read_thread exit by always firing an event (#27)
* Ensure an event is triggered when the grpc_read_thread exits in all cases

* Compare correct status codes for v1 and v2

* Add sign-off to previous commit

Signed-off-by: Andrew Golledge <andreas.golledge@gmail.com>

---------

Signed-off-by: Andrew Golledge <andreas.golledge@gmail.com>
2024-03-26 09:18:56 -04:00
Dave Horton 48aeedec0a bug: fix crashing race condition in mod_dub 2024-03-25 18:14:54 -04:00
Hoan Luu Huu be6758c3a8 support azure tts stream (#13)
* support azure stream

* delete trash file

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fire variable_tts_time_to_first_byte_ms
2024-03-24 09:18:38 -04:00
20 changed files with 1178 additions and 74 deletions
+10 -2
View File
@@ -2,9 +2,17 @@ include $(top_srcdir)/build/modmake.rulesam
MODNAME=mod_audio_fork MODNAME=mod_audio_fork
mod_LTLIBRARIES = mod_audio_fork.la mod_LTLIBRARIES = mod_audio_fork.la
mod_audio_fork_la_SOURCES = mod_audio_fork.c lws_glue.cpp parser.cpp audio_pipe.cpp mod_audio_fork_la_SOURCES = mod_audio_fork.c lws_glue.cpp parser.cpp audio_pipe.cpp vector_math.cpp
mod_audio_fork_la_CFLAGS = $(AM_CFLAGS) mod_audio_fork_la_CFLAGS = $(AM_CFLAGS)
mod_audio_fork_la_CXXFLAGS = $(AM_CXXFLAGS) -std=c++11 mod_audio_fork_la_CXXFLAGS = $(AM_CXXFLAGS) -std=c++11
if USE_AVX2
mod_audio_fork_la_CXXFLAGS += -mavx2 -DUSE_AVX2
else
if USE_SSE2
mod_audio_fork_la_CXXFLAGS += -msse2 -DUSE_SSE2
endif
endif
mod_audio_fork_la_LIBADD = $(switch_builddir)/libfreeswitch.la mod_audio_fork_la_LIBADD = $(switch_builddir)/libfreeswitch.la
mod_audio_fork_la_LDFLAGS = -avoid-version -module -no-undefined -shared `pkg-config --libs libwebsockets` mod_audio_fork_la_LDFLAGS = -avoid-version -module -no-undefined -shared `pkg-config --libs libwebsockets` -lstdc++
+14 -7
View File
@@ -1,4 +1,5 @@
#include "audio_pipe.hpp" #include "audio_pipe.hpp"
#include <switch.h>
#include <cassert> #include <cassert>
#include <iostream> #include <iostream>
@@ -83,7 +84,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); lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR: %s, response status %d\n", in ? (char *)in : "(null)", rc);
if (ap) { if (ap) {
ap->m_state = LWS_CLIENT_FAILED; 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->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECT_FAIL, (char *) in, NULL, len);
} }
else { else {
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR unable to find wsi %p..\n", wsi); lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_CONNECTION_ERROR unable to find wsi %p..\n", wsi);
@@ -98,7 +99,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
*ppAp = ap; *ppAp = ap;
ap->m_vhd = vhd; ap->m_vhd = vhd;
ap->m_state = LWS_CLIENT_CONNECTED; ap->m_state = LWS_CLIENT_CONNECTED;
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECT_SUCCESS, NULL); ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECT_SUCCESS, NULL, NULL, len);
} }
else { else {
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_ESTABLISHED %s unable to find wsi %p..\n", ap->m_uuid.c_str(), wsi); lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_ESTABLISHED %s unable to find wsi %p..\n", ap->m_uuid.c_str(), wsi);
@@ -114,12 +115,12 @@ int AudioPipe::lws_callback(struct lws *wsi,
} }
if (ap->m_state == LWS_CLIENT_DISCONNECTING) { if (ap->m_state == LWS_CLIENT_DISCONNECTING) {
// closed by us // closed by us
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECTION_CLOSED_GRACEFULLY, NULL); ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECTION_CLOSED_GRACEFULLY, NULL, NULL, len);
} }
else if (ap->m_state == LWS_CLIENT_CONNECTED) { else if (ap->m_state == LWS_CLIENT_CONNECTED) {
// closed by far end // closed by far end
lwsl_notice("%s socket closed by far end\n", ap->m_uuid.c_str()); lwsl_notice("%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->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::CONNECTION_DROPPED, NULL, NULL, len);
} }
ap->m_state = LWS_CLIENT_DISCONNECTED; ap->m_state = LWS_CLIENT_DISCONNECTED;
@@ -133,6 +134,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
case LWS_CALLBACK_CLIENT_RECEIVE: case LWS_CALLBACK_CLIENT_RECEIVE:
{ {
AudioPipe* ap = *ppAp; AudioPipe* ap = *ppAp;
if (!ap) { 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); lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_RECEIVE %s unable to find wsi %p..\n", ap->m_uuid.c_str(), wsi);
@@ -140,7 +142,11 @@ int AudioPipe::lws_callback(struct lws *wsi,
} }
if (lws_frame_is_binary(wsi)) { if (lws_frame_is_binary(wsi)) {
if (ap->is_bidirectional_audio_stream()) {
ap->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::BINARY, NULL, (char *) in, len);
} else {
lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_RECEIVE received binary frame, discarding.\n"); lwsl_err("AudioPipe::lws_service_thread LWS_CALLBACK_CLIENT_RECEIVE received binary frame, discarding.\n");
}
return 0; return 0;
} }
@@ -180,7 +186,7 @@ int AudioPipe::lws_callback(struct lws *wsi,
if (lws_is_final_fragment(wsi)) { if (lws_is_final_fragment(wsi)) {
if (nullptr != ap->m_recv_buf) { if (nullptr != ap->m_recv_buf) {
std::string msg((char *)ap->m_recv_buf, ap->m_recv_buf_ptr - 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->m_callback(ap->m_uuid.c_str(), ap->m_bugname.c_str(), AudioPipe::MESSAGE, msg.c_str(), NULL, len);
if (nullptr != ap->m_recv_buf) free(ap->m_recv_buf); if (nullptr != ap->m_recv_buf) free(ap->m_recv_buf);
} }
ap->m_recv_buf = ap->m_recv_buf_ptr = nullptr; ap->m_recv_buf = ap->m_recv_buf_ptr = nullptr;
@@ -458,7 +464,8 @@ bool AudioPipe::deinitialize() {
// instance members // instance members
AudioPipe::AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path, AudioPipe::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) : int sslFlags, size_t bufLen, size_t minFreespace, const char* username, const char* password, char* bugname,
int bidirectional_audio_stream, notifyHandler_t callback) :
m_uuid(uuid), m_host(host), m_port(port), m_path(path), m_sslFlags(sslFlags), m_uuid(uuid), m_host(host), m_port(port), m_path(path), m_sslFlags(sslFlags),
m_audio_buffer_min_freespace(minFreespace), m_audio_buffer_max_len(bufLen), m_gracefulShutdown(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_bugname(bugname), m_audio_buffer_write_offset(LWS_PRE), m_recv_buf(nullptr), m_recv_buf_ptr(nullptr), m_bugname(bugname),
@@ -468,7 +475,7 @@ AudioPipe::AudioPipe(const char* uuid, const char* host, unsigned int port, cons
m_username.assign(username); m_username.assign(username);
m_password.assign(password); m_password.assign(password);
} }
m_bidirectional_audio_stream = bidirectional_audio_stream;
m_audio_buffer = new uint8_t[m_audio_buffer_max_len]; m_audio_buffer = new uint8_t[m_audio_buffer_max_len];
} }
AudioPipe::~AudioPipe() { AudioPipe::~AudioPipe() {
+10 -3
View File
@@ -27,10 +27,11 @@ namespace drachtio {
CONNECT_FAIL, CONNECT_FAIL,
CONNECTION_DROPPED, CONNECTION_DROPPED,
CONNECTION_CLOSED_GRACEFULLY, CONNECTION_CLOSED_GRACEFULLY,
MESSAGE MESSAGE,
BINARY
}; };
typedef void (*log_emit_function)(int level, const char *line); 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); typedef void (*notifyHandler_t)(const char *sessionId, const char* bugname, NotifyEvent_t event, const char* message, const char* binary, size_t binary_len );
struct lws_per_vhost_data { struct lws_per_vhost_data {
struct lws_context *context; struct lws_context *context;
@@ -44,7 +45,8 @@ namespace drachtio {
// constructor // constructor
AudioPipe(const char* uuid, const char* host, unsigned int port, const char* path, int sslFlags, 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); size_t bufLen, size_t minFreespace, const char* username, const char* password, char* bugname,
int bidirectional_audio, notifyHandler_t callback);
~AudioPipe(); ~AudioPipe();
LwsState_t getLwsState(void) { return m_state; } LwsState_t getLwsState(void) { return m_state; }
@@ -83,6 +85,10 @@ namespace drachtio {
return m_gracefulShutdown; return m_gracefulShutdown;
} }
bool is_bidirectional_audio_stream() {
return m_bidirectional_audio_stream;
}
void close() ; void close() ;
// no default constructor or copying // no default constructor or copying
@@ -142,6 +148,7 @@ namespace drachtio {
std::string m_username; std::string m_username;
std::string m_password; std::string m_password;
bool m_gracefulShutdown; bool m_gracefulShutdown;
bool m_bidirectional_audio_stream;
}; };
} // namespace drachtio } // namespace drachtio
+137 -6
View File
@@ -17,9 +17,15 @@
#include "parser.hpp" #include "parser.hpp"
#include "mod_audio_fork.h" #include "mod_audio_fork.h"
#include "audio_pipe.hpp" #include "audio_pipe.hpp"
#include "vector_math.h"
#include <boost/circular_buffer.hpp>
typedef boost::circular_buffer<uint16_t> CircularBuffer_t;
#define RTP_PACKETIZATION_PERIOD 20 #define RTP_PACKETIZATION_PERIOD 20
#define FRAME_SIZE_8000 320 /*which means each 20ms frame as 320 bytes at 8 khz (1 channel only)*/ #define FRAME_SIZE_8000 320 /*which means each 20ms frame as 320 bytes at 8 khz (1 channel only)*/
#define BUFFER_GROW_SIZE (8192)
namespace { namespace {
static const char *requestedBufferSecs = std::getenv("MOD_AUDIO_FORK_BUFFER_SECS"); static const char *requestedBufferSecs = std::getenv("MOD_AUDIO_FORK_BUFFER_SECS");
@@ -31,6 +37,48 @@ namespace {
static unsigned int idxCallCount = 0; static unsigned int idxCallCount = 0;
static uint32_t playCount = 0; static uint32_t playCount = 0;
switch_status_t processIncomingBinary(private_t* tech_pvt, switch_core_session_t* session, const char* message, size_t dataLength) {
CircularBuffer_t *cBuffer = (CircularBuffer_t *) tech_pvt->circularBuffer;
uint8_t* data = reinterpret_cast<uint8_t*>(const_cast<char*>(message));
uint16_t* data_uint16 = reinterpret_cast<uint16_t*>(data);
std::vector<uint16_t> pcm_data(data_uint16, data_uint16 + dataLength / sizeof(uint16_t));
if (tech_pvt->bidirectional_audio_resampler) {
std::vector<int16_t> in(pcm_data.begin(), pcm_data.end());
std::vector<int16_t> out(dataLength);
spx_uint32_t in_len = pcm_data.size();
spx_uint32_t out_len = out.size();
speex_resampler_process_interleaved_int(tech_pvt->bidirectional_audio_resampler, in.data(), &in_len, out.data(), &out_len);
if (out_len > out.size()) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Resampler output exceeded maximum buffer size!\n");
return SWITCH_STATUS_FALSE;
}
// Resize the pcm_data to match the output length from resampler, and then copy the resampled data into it.
pcm_data.resize(out_len);
memcpy(pcm_data.data(), out.data(), out_len * sizeof(int16_t));
}
switch_mutex_lock(tech_pvt->mutex);
// Resize the buffer if necessary
size_t bytesResampled = pcm_data.size() * sizeof(uint16_t);
if (cBuffer->capacity() - cBuffer->size() < bytesResampled / sizeof(uint16_t)) {
// If buffer exceeds some max size, you could return SWITCH_STATUS_FALSE to abort the transfer
// if (cBuffer->size() + std::max(bytesResampled / sizeof(uint16_t), (size_t)BUFFER_GROW_SIZE) > MAX_BUFFER_SIZE) return SWITCH_STATUS_FALSE;
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.begin(), pcm_data.end());
switch_mutex_unlock(tech_pvt->mutex);
return SWITCH_STATUS_SUCCESS;
}
void processIncomingMessage(private_t* tech_pvt, switch_core_session_t* session, const char* message) { void processIncomingMessage(private_t* tech_pvt, switch_core_session_t* session, const char* message) {
std::string msg = message; std::string msg = message;
std::string type; std::string type;
@@ -38,7 +86,10 @@ namespace {
if (json) { if (json) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "(%u) processIncomingMessage - received %s message\n", tech_pvt->id, type.c_str()); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "(%u) processIncomingMessage - received %s message\n", tech_pvt->id, type.c_str());
cJSON* jsonData = cJSON_GetObjectItem(json, "data"); cJSON* jsonData = cJSON_GetObjectItem(json, "data");
if (0 == type.compare("playAudio")) { if (0 == type.compare("playAudio") &&
// playAudio is enabled and there is no bidirectional audio from stream is enabled.
tech_pvt->bidirectional_audio_enable &&
!tech_pvt->bidirectional_audio_stream) {
if (jsonData) { if (jsonData) {
// dont send actual audio bytes in event message // dont send actual audio bytes in event message
cJSON* jsonFile = NULL; cJSON* jsonFile = NULL;
@@ -156,7 +207,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, drachtio::AudioPipe::NotifyEvent_t event, const char* message, const char* binary, size_t len) {
switch_core_session_t* session = switch_core_session_locate(sessionId); switch_core_session_t* session = switch_core_session_locate(sessionId);
if (session) { if (session) {
switch_channel_t *channel = switch_core_session_get_channel(session); switch_channel_t *channel = switch_core_session_get_channel(session);
@@ -198,6 +249,9 @@ namespace {
case drachtio::AudioPipe::MESSAGE: case drachtio::AudioPipe::MESSAGE:
processIncomingMessage(tech_pvt, session, message); processIncomingMessage(tech_pvt, session, message);
break; break;
case drachtio::AudioPipe::BINARY:
processIncomingBinary(tech_pvt, session, binary, len);
break;
} }
} }
} }
@@ -206,11 +260,13 @@ namespace {
} }
switch_status_t fork_data_init(private_t *tech_pvt, switch_core_session_t *session, char * host, switch_status_t fork_data_init(private_t *tech_pvt, switch_core_session_t *session, char * host,
unsigned int port, char* path, int sslFlags, int sampling, int desiredSampling, int channels, unsigned int port, char* path, int sslFlags, int sampling, int desiredSampling, int channels,
char *bugname, char* metadata, responseHandler_t responseHandler) { char *bugname, char* metadata, int bidirectional_audio_enable,
int bidirectional_audio_stream, int bidirectional_audio_sample_rate, responseHandler_t responseHandler) {
const char* username = nullptr; const char* username = nullptr;
const char* password = nullptr; const char* password = nullptr;
int err; int err;
int bidirectional_audio_stream_enable = bidirectional_audio_enable + bidirectional_audio_stream;
switch_codec_implementation_t read_impl; switch_codec_implementation_t read_impl;
switch_channel_t *channel = switch_core_session_get_channel(session); switch_channel_t *channel = switch_core_session_get_channel(session);
@@ -234,13 +290,17 @@ namespace {
tech_pvt->buffer_overrun_notified = 0; tech_pvt->buffer_overrun_notified = 0;
tech_pvt->audio_paused = 0; tech_pvt->audio_paused = 0;
tech_pvt->graceful_shutdown = 0; tech_pvt->graceful_shutdown = 0;
tech_pvt->circularBuffer = (void *) new CircularBuffer_t(8192);
tech_pvt->bidirectional_audio_enable = bidirectional_audio_enable;
tech_pvt->bidirectional_audio_stream = bidirectional_audio_stream;
tech_pvt->bidirectional_audio_sample_rate = bidirectional_audio_sample_rate;
strncpy(tech_pvt->bugname, bugname, MAX_BUG_LEN); strncpy(tech_pvt->bugname, bugname, MAX_BUG_LEN);
if (metadata) strncpy(tech_pvt->initialMetadata, metadata, MAX_METADATA_LEN); if (metadata) strncpy(tech_pvt->initialMetadata, metadata, MAX_METADATA_LEN);
size_t buflen = LWS_PRE + (FRAME_SIZE_8000 * desiredSampling / 8000 * channels * 1000 / RTP_PACKETIZATION_PERIOD * nAudioBufferSecs); 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, drachtio::AudioPipe* ap = new drachtio::AudioPipe(tech_pvt->sessionId, host, port, path, sslFlags,
buflen, read_impl.decoded_bytes_per_packet, username, password, bugname, eventCallback); buflen, read_impl.decoded_bytes_per_packet, username, password, bugname, bidirectional_audio_stream_enable, eventCallback);
if (!ap) { if (!ap) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error allocating AudioPipe\n"); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error allocating AudioPipe\n");
return SWITCH_STATUS_FALSE; return SWITCH_STATUS_FALSE;
@@ -262,6 +322,15 @@ namespace {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) no resampling needed for this call\n", tech_pvt->id); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) no resampling needed for this call\n", tech_pvt->id);
} }
if (bidirectional_audio_sample_rate && sampling != bidirectional_audio_sample_rate) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) bidirectional audio resampling from %u to %u\n", tech_pvt->id, bidirectional_audio_sample_rate, sampling);
tech_pvt->bidirectional_audio_resampler = speex_resampler_init(channels, bidirectional_audio_sample_rate, sampling, SWITCH_RESAMPLE_QUALITY, &err);
if (0 != err) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error initializing bidirectional audio resampler: %s.\n", speex_resampler_strerror(err));
return SWITCH_STATUS_FALSE;
}
}
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) fork_data_init\n", tech_pvt->id); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) fork_data_init\n", tech_pvt->id);
return SWITCH_STATUS_SUCCESS; return SWITCH_STATUS_SUCCESS;
@@ -273,10 +342,19 @@ namespace {
speex_resampler_destroy(tech_pvt->resampler); speex_resampler_destroy(tech_pvt->resampler);
tech_pvt->resampler = nullptr; tech_pvt->resampler = nullptr;
} }
if (tech_pvt->bidirectional_audio_resampler) {
speex_resampler_destroy(tech_pvt->bidirectional_audio_resampler);
tech_pvt->bidirectional_audio_resampler = nullptr;
}
if (tech_pvt->mutex) { if (tech_pvt->mutex) {
switch_mutex_destroy(tech_pvt->mutex); switch_mutex_destroy(tech_pvt->mutex);
tech_pvt->mutex = nullptr; tech_pvt->mutex = nullptr;
} }
if (tech_pvt->circularBuffer) {
CircularBuffer_t *cBuffer = (CircularBuffer_t *) tech_pvt->circularBuffer;
delete cBuffer;
tech_pvt->circularBuffer = nullptr;
}
} }
void lws_logger(int level, const char *line) { void lws_logger(int level, const char *line) {
@@ -402,7 +480,11 @@ extern "C" {
int channels, int channels,
char *bugname, char *bugname,
char* metadata, char* metadata,
void **ppUserData) int bidirectional_audio_enable,
int bidirectional_audio_stream,
int bidirectional_audio_sample_rate,
void **ppUserData
)
{ {
int err; int err;
@@ -412,8 +494,9 @@ extern "C" {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "error allocating memory!\n"); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "error allocating memory!\n");
return SWITCH_STATUS_FALSE; return SWITCH_STATUS_FALSE;
} }
if (SWITCH_STATUS_SUCCESS != fork_data_init(tech_pvt, session, host, port, path, sslFlags, samples_per_second, sampling, channels, if (SWITCH_STATUS_SUCCESS != fork_data_init(tech_pvt, session, host, port, path, sslFlags, samples_per_second, sampling, channels,
bugname, metadata, responseHandler)) { bugname, metadata, bidirectional_audio_enable, bidirectional_audio_stream, bidirectional_audio_sample_rate, responseHandler)) {
destroy_tech_pvt(tech_pvt); destroy_tech_pvt(tech_pvt);
return SWITCH_STATUS_FALSE; return SWITCH_STATUS_FALSE;
} }
@@ -619,5 +702,53 @@ extern "C" {
return SWITCH_TRUE; return SWITCH_TRUE;
} }
switch_bool_t dub_speech_frame(switch_media_bug_t *bug, private_t* tech_pvt) {
CircularBuffer_t *cBuffer = (CircularBuffer_t *) tech_pvt->circularBuffer;
if (switch_mutex_trylock(tech_pvt->mutex) == SWITCH_STATUS_SUCCESS) {
switch_frame_t* rframe = switch_core_media_bug_get_write_replace_frame(bug);
int16_t *fp = reinterpret_cast<int16_t*>(rframe->data);
rframe->channels = 1;
rframe->datalen = rframe->samples * sizeof(int16_t);
int16_t data[SWITCH_RECOMMENDED_BUFFER_SIZE];
memset(data, 0, sizeof(data));
int samplesToCopy = std::min(static_cast<int>(cBuffer->size()), static_cast<int>(rframe->samples));
std::copy_n(cBuffer->begin(), samplesToCopy, data);
cBuffer->erase(cBuffer->begin(), cBuffer->begin() + samplesToCopy);
if (samplesToCopy > 0) {
vector_add(fp, data, rframe->samples);
}
vector_normalize(fp, rframe->samples);
switch_core_media_bug_set_write_replace_frame(bug, rframe);
switch_mutex_unlock(tech_pvt->mutex);
}
return SWITCH_TRUE;
}
switch_status_t fork_session_stop_play(switch_core_session_t *session, 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);
if (!bug) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "fork_session_stop_play failed because no bug\n");
return SWITCH_STATUS_FALSE;
}
private_t* tech_pvt = (private_t*) switch_core_media_bug_get_user_data(bug);
CircularBuffer_t *cBuffer = (CircularBuffer_t *) tech_pvt->circularBuffer;
if (switch_mutex_trylock(tech_pvt->mutex) == SWITCH_STATUS_SUCCESS) {
if (cBuffer != nullptr) {
cBuffer->clear();
}
switch_mutex_unlock(tech_pvt->mutex);
}
return SWITCH_STATUS_SUCCESS;
}
} }
+4 -1
View File
@@ -9,12 +9,15 @@ switch_status_t fork_init();
switch_status_t fork_cleanup(); switch_status_t fork_cleanup();
switch_status_t fork_session_init(switch_core_session_t *session, responseHandler_t responseHandler, switch_status_t fork_session_init(switch_core_session_t *session, responseHandler_t responseHandler,
uint32_t samples_per_second, char *host, unsigned int port, char* path, int sampling, int sslFlags, int channels, uint32_t samples_per_second, char *host, unsigned int port, char* path, int sampling, int sslFlags, int channels,
char *bugname, char* metadata, void **ppUserData); char *bugname, char* metadata, int bidirectional_audio_enable,
int bidirectional_audio_stream, int bidirectional_audio_sample_rate, void **ppUserData);
switch_status_t fork_session_cleanup(switch_core_session_t *session, char *bugname, char* text, int channelIsClosing); switch_status_t fork_session_cleanup(switch_core_session_t *session, char *bugname, char* text, int channelIsClosing);
switch_status_t fork_session_stop_play(switch_core_session_t *session, char *bugname);
switch_status_t fork_session_pauseresume(switch_core_session_t *session, char *bugname, int pause); switch_status_t fork_session_pauseresume(switch_core_session_t *session, char *bugname, int pause);
switch_status_t fork_session_graceful_shutdown(switch_core_session_t *session, char *bugname); switch_status_t fork_session_graceful_shutdown(switch_core_session_t *session, char *bugname);
switch_status_t fork_session_send_text(switch_core_session_t *session, char *bugname, char* text); switch_status_t fork_session_send_text(switch_core_session_t *session, char *bugname, char* text);
switch_bool_t fork_frame(switch_core_session_t *session, switch_media_bug_t *bug); switch_bool_t fork_frame(switch_core_session_t *session, switch_media_bug_t *bug);
switch_bool_t dub_speech_frame(switch_media_bug_t *bug, private_t * tech_pvt);
switch_status_t fork_service_threads(); switch_status_t fork_service_threads();
switch_status_t fork_session_connect(void **ppUserData); switch_status_t fork_session_connect(void **ppUserData);
#endif #endif
+57 -11
View File
@@ -27,21 +27,26 @@ static void responseHandler(switch_core_session_t* session, const char * eventNa
static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type) static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
{ {
switch_bool_t ret = SWITCH_TRUE;
switch_core_session_t *session = switch_core_media_bug_get_session(bug); switch_core_session_t *session = switch_core_media_bug_get_session(bug);
private_t* tech_pvt = (private_t *) switch_core_media_bug_get_user_data(bug);
switch (type) { switch (type) {
case SWITCH_ABC_TYPE_INIT: case SWITCH_ABC_TYPE_INIT:
break; break;
case SWITCH_ABC_TYPE_CLOSE: case SWITCH_ABC_TYPE_CLOSE:
{ {
private_t* tech_pvt = (private_t *) switch_core_media_bug_get_user_data(bug);
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Got SWITCH_ABC_TYPE_CLOSE for bug %s\n", tech_pvt->bugname); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Got SWITCH_ABC_TYPE_CLOSE for bug %s\n", tech_pvt->bugname);
fork_session_cleanup(session, tech_pvt->bugname, NULL, 1); fork_session_cleanup(session, tech_pvt->bugname, NULL, 1);
} }
break; break;
case SWITCH_ABC_TYPE_READ: case SWITCH_ABC_TYPE_READ:
return fork_frame(session, bug); ret = fork_frame(session, bug);
break;
case SWITCH_ABC_TYPE_WRITE_REPLACE:
ret = dub_speech_frame(bug, tech_pvt);
break; break;
case SWITCH_ABC_TYPE_WRITE: case SWITCH_ABC_TYPE_WRITE:
@@ -49,7 +54,7 @@ static switch_bool_t capture_callback(switch_media_bug_t *bug, void *user_data,
break; break;
} }
return SWITCH_TRUE; return ret;
} }
static switch_status_t start_capture(switch_core_session_t *session, static switch_status_t start_capture(switch_core_session_t *session,
@@ -59,6 +64,9 @@ static switch_status_t start_capture(switch_core_session_t *session,
char* path, char* path,
int sampling, int sampling,
int sslFlags, int sslFlags,
int bidirectional_audio_enable,
int bidirectional_audio_stream,
int bidirectional_audio_sample_rate,
char* bugname, char* bugname,
char* metadata) char* metadata)
{ {
@@ -71,8 +79,8 @@ static switch_status_t start_capture(switch_core_session_t *session,
int channels = (flags & SMBF_STEREO) ? 2 : 1; int channels = (flags & SMBF_STEREO) ? 2 : 1;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO,
"mod_audio_fork (%s): streaming %d sampling to %s path %s port %d tls: %s.\n", "mod_audio_fork (%s): streaming %d sampling to %s path %s port %d tls: %s bidirectional_audio_sample_rate: %d.\n",
bugname, sampling, host, path, port, sslFlags ? "yes" : "no"); bugname, sampling, host, path, port, sslFlags ? "yes" : "no", bidirectional_audio_sample_rate);
if (switch_channel_get_private(channel, bugname)) { if (switch_channel_get_private(channel, bugname)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "mod_audio_fork: bug %s already attached!\n", bugname); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "mod_audio_fork: bug %s already attached!\n", bugname);
@@ -88,10 +96,12 @@ static switch_status_t start_capture(switch_core_session_t *session,
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "calling fork_session_init.\n"); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "calling fork_session_init.\n");
if (SWITCH_STATUS_FALSE == fork_session_init(session, responseHandler, read_codec->implementation->actual_samples_per_second, if (SWITCH_STATUS_FALSE == fork_session_init(session, responseHandler, read_codec->implementation->actual_samples_per_second,
host, port, path, sampling, sslFlags, channels, bugname, metadata, &pUserData)) { host, port, path, sampling, sslFlags, channels, bugname, metadata, bidirectional_audio_enable, bidirectional_audio_stream,
bidirectional_audio_sample_rate, &pUserData)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error initializing mod_audio_fork session.\n"); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error initializing mod_audio_fork session.\n");
return SWITCH_STATUS_FALSE; return SWITCH_STATUS_FALSE;
} }
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "adding bug %s.\n", bugname); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "adding bug %s.\n", bugname);
if ((status = switch_core_media_bug_add(session, bugname, NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) { if ((status = switch_core_media_bug_add(session, bugname, NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
return status; return status;
@@ -133,6 +143,16 @@ static switch_status_t do_pauseresume(switch_core_session_t *session, char* bugn
return status; return status;
} }
static switch_status_t stop_play(switch_core_session_t *session, char* bugname)
{
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "mod_audio_fork stop_play\n");
status = fork_session_stop_play(session, bugname);
return status;
}
static switch_status_t do_graceful_shutdown(switch_core_session_t *session, char* bugname) static switch_status_t do_graceful_shutdown(switch_core_session_t *session, char* bugname)
{ {
switch_status_t status = SWITCH_STATUS_SUCCESS; switch_status_t status = SWITCH_STATUS_SUCCESS;
@@ -159,10 +179,10 @@ static switch_status_t send_text(switch_core_session_t *session, char* bugname,
return status; return status;
} }
#define FORK_API_SYNTAX "<uuid> [start | stop | send_text | pause | resume | graceful-shutdown ] [wss-url | path] [mono | mixed | stereo] [8000 | 16000 | 24000 | 32000 | 64000] [bugname] [metadata]" #define FORK_API_SYNTAX "<uuid> [start | stop | send_text | pause | resume | graceful-shutdown | stop_play ] [wss-url | path] [mono | mixed | stereo] [8000 | 16000 | 24000 | 32000 | 64000] [bugname] [metadata] [bidirectionalAudio_enabled] [bidirectionalAudio_stream_enabled] [bidirectionalAudio_stream_samplerate]"
SWITCH_STANDARD_API(fork_function) SWITCH_STANDARD_API(fork_function)
{ {
char *mycmd = NULL, *argv[7] = { 0 }; char *mycmd = NULL, *argv[10] = { 0 };
int argc = 0; int argc = 0;
switch_status_t status = SWITCH_STATUS_FALSE; switch_status_t status = SWITCH_STATUS_FALSE;
char *bugname = MY_BUG_NAME; char *bugname = MY_BUG_NAME;
@@ -195,6 +215,9 @@ SWITCH_STANDARD_API(fork_function)
else bugname = argv[2]; else bugname = argv[2];
} }
status = do_stop(lsession, bugname, text); status = do_stop(lsession, bugname, text);
}
else if (!strcasecmp(argv[1], "stop_play")) {
status = stop_play(lsession, bugname);
} }
else if (!strcasecmp(argv[1], "pause")) { else if (!strcasecmp(argv[1], "pause")) {
if (argc > 2) bugname = argv[2]; if (argc > 2) bugname = argv[2];
@@ -230,10 +253,31 @@ SWITCH_STANDARD_API(fork_function)
char host[MAX_WS_URL_LEN], path[MAX_PATH_LEN]; char host[MAX_WS_URL_LEN], path[MAX_PATH_LEN];
unsigned int port; unsigned int port;
int sslFlags; int sslFlags;
int sampling = 8000; int sampling = 8000;
switch_media_bug_flag_t flags = SMBF_READ_STREAM ; switch_media_bug_flag_t flags = SMBF_READ_STREAM;
char *metadata = NULL; char *metadata = NULL;
if( argc > 6) { int bidirectional_audio_enable = 1;
int bidirectional_audio_stream = 0;
int bidirectional_audio_sample_rate = 0;
// Expecting that bidirectional audio params is always received together with bugname and metadata even they are empty string
if (argc > 9) {
if (argv[5][0] != '\0') {
bugname = argv[5];
}
if (argv[6][0] != '\0') {
metadata = argv[6];
}
bidirectional_audio_enable = !strcmp(argv[7], "true") ? 1 : 0;
bidirectional_audio_stream = !strcmp(argv[8], "true") ? 1 : 0;
bidirectional_audio_sample_rate = atoi(argv[9]);
if (bidirectional_audio_enable &&
bidirectional_audio_stream &&
bidirectional_audio_sample_rate) {
flags |= SMBF_WRITE_REPLACE ;
}
} else if( argc > 6 ) {
bugname = argv[5]; bugname = argv[5];
metadata = argv[6]; metadata = argv[6];
} }
@@ -241,6 +285,7 @@ SWITCH_STANDARD_API(fork_function)
if (argv[5][0] == '{' || argv[5][0] == '[') metadata = argv[5]; if (argv[5][0] == '{' || argv[5][0] == '[') metadata = argv[5];
else bugname = argv[5]; else bugname = argv[5];
} }
if (0 == strcmp(argv[3], "mixed")) { if (0 == strcmp(argv[3], "mixed")) {
flags |= SMBF_WRITE_STREAM ; flags |= SMBF_WRITE_STREAM ;
} }
@@ -268,7 +313,8 @@ SWITCH_STANDARD_API(fork_function)
else if (sampling % 8000 != 0) { else if (sampling % 8000 != 0) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "invalid sample rate: %s\n", argv[4]); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "invalid sample rate: %s\n", argv[4]);
} }
status = start_capture(lsession, flags, host, port, path, sampling, sslFlags, bugname, metadata); status = start_capture(lsession, flags, host, port, path, sampling, sslFlags,
bidirectional_audio_enable, bidirectional_audio_stream, bidirectional_audio_sample_rate, bugname, metadata);
} }
else { else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "unsupported mod_audio_fork cmd: %s\n", argv[1]); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "unsupported mod_audio_fork cmd: %s\n", argv[1]);
+5
View File
@@ -52,6 +52,11 @@ struct private_data {
int audio_paused:1; int audio_paused:1;
int graceful_shutdown:1; int graceful_shutdown:1;
char initialMetadata[8192]; char initialMetadata[8192];
void *circularBuffer;
SpeexResamplerState *bidirectional_audio_resampler;
int bidirectional_audio_enable;
int bidirectional_audio_stream;
int bidirectional_audio_sample_rate;
}; };
typedef struct private_data private_t; typedef struct private_data private_t;
+211
View File
@@ -0,0 +1,211 @@
#include "vector_math.h"
#include <assert.h>
#include <string.h>
#define GRANULAR_VOLUME_MAX (50)
#define SMAX 32767
#define SMIN (-32768)
#define normalize_to_16bit_basic(n) if (n > SMAX) n = SMAX; else if (n < SMIN) n = SMIN;
#define normalize_volume_granular(x) if (x > GRANULAR_VOLUME_MAX) x = GRANULAR_VOLUME_MAX; if (x < -GRANULAR_VOLUME_MAX) x = -GRANULAR_VOLUME_MAX;
#ifdef __cplusplus
extern "C" {
#endif
#if defined(USE_AVX2)
#include <immintrin.h>
#pragma message("Using AVX2 SIMD.")
void vector_add(int16_t* a, int16_t* b, size_t len) {
size_t i = 0;
for (; i + 15 < len; i += 16) {
__m256i va = _mm256_loadu_si256((const __m256i*)(a + i));
__m256i vb = _mm256_loadu_si256((const __m256i*)(b + i));
__m256i vc = _mm256_add_epi16(va, vb);
_mm256_storeu_si256((__m256i*)(a + i), vc);
}
for (; i < len; ++i) {
a[i] += b[i];
}
}
void vector_normalize(int16_t* a, size_t len) {
__m256i max_val = _mm256_set1_epi16(SMAX);
__m256i min_val = _mm256_set1_epi16(SMIN);
size_t i = 0;
for (; i + 15 < len; i += 16) {
__m256i values = _mm256_loadu_si256((__m256i*)(a + i));
__m256i gt_max = _mm256_cmpgt_epi16(values, max_val);
__m256i lt_min = _mm256_cmpgt_epi16(min_val, values);
values = _mm256_blendv_epi8(values, max_val, gt_max);
values = _mm256_blendv_epi8(values, min_val, lt_min);
_mm256_storeu_si256((__m256i*)(a + i), values);
}
// Process remaining elements
for (; i < len; ++i) {
if (a[i] > SMAX) a[i] = SMAX;
else if (a[i] < SMIN) a[i] = SMIN;
}
}
typedef union {
int16_t* data;
__m256i* fp_avx2;
} vector_data_t;
void vector_change_sln_volume_granular(int16_t* data, uint32_t samples, int32_t vol) {
float newrate = 0;
static const float pos[GRANULAR_VOLUME_MAX] = {
1.122018, 1.258925, 1.412538, 1.584893, 1.778279, 1.995262, 2.238721, 2.511887, 2.818383, 3.162278,
3.548134, 3.981072, 4.466835, 5.011872, 5.623413, 6.309574, 7.079458, 7.943282, 8.912509, 10.000000,
11.220183, 12.589254, 14.125375, 15.848933, 17.782795, 19.952621, 22.387213, 25.118862, 28.183832, 31.622776,
35.481335, 39.810719, 44.668358, 50.118729, 56.234131, 63.095726, 70.794586, 79.432816, 89.125107, 100.000000,
112.201836, 125.892517, 141.253784, 158.489334, 177.827942, 199.526215, 223.872070, 251.188705, 281.838318, 316.227753
};
static const float neg[GRANULAR_VOLUME_MAX] = {
0.891251, 0.794328, 0.707946, 0.630957, 0.562341, 0.501187, 0.446684, 0.398107, 0.354813, 0.316228,
0.281838, 0.251189, 0.223872, 0.199526, 0.177828, 0.158489, 0.141254, 0.125893, 0.112202, 0.100000,
0.089125, 0.079433, 0.070795, 0.063096, 0.056234, 0.050119, 0.044668, 0.039811, 0.035481, 0.031623,
0.028184, 0.025119, 0.022387, 0.019953, 0.017783, 0.015849, 0.014125, 0.012589, 0.011220, 0.010000,
0.008913, 0.007943, 0.007079, 0.006310, 0.005623, 0.005012, 0.004467, 0.003981, 0.003548, 0.000000 // NOTE mapped -50 dB ratio to total silence instead of 0.003162
};
const float* chart;
uint32_t i = abs(vol) - 1;
if (vol == 0) return;
normalize_volume_granular(vol);
chart = vol > 0 ? pos : neg;
newrate = chart[i];
if (newrate) {
__m256 scale_factor_reg = _mm256_set1_ps(newrate);
uint32_t processed_samples = samples - (samples % 8); // Ensure we process only multiples of 8
for (uint32_t i = 0; i < processed_samples; i += 8) {
__m128i data_ = _mm_loadu_si128((__m128i*)(data + i));
__m256i data_32 = _mm256_cvtepi16_epi32(data_);
__m256 data_float = _mm256_cvtepi32_ps(data_32);
__m256 result = _mm256_mul_ps(data_float, scale_factor_reg);
__m256i result_32 = _mm256_cvtps_epi32(result);
// Handle saturation
__m256i min_val = _mm256_set1_epi32(SMIN);
__m256i max_val = _mm256_set1_epi32(SMAX);
result_32 = _mm256_min_epi32(result_32, max_val);
result_32 = _mm256_max_epi32(result_32, min_val);
__m128i result_16 = _mm_packs_epi32(_mm256_castsi256_si128(result_32), _mm256_extractf128_si256(result_32, 1));
_mm_storeu_si128((__m128i*)(data + i), result_16);
}
// Process any remaining samples
for (uint32_t i = processed_samples; i < samples; i++) {
int32_t tmp = (int32_t)(data[i] * newrate);
tmp = tmp > SMAX ? SMAX : (tmp < SMIN ? SMIN : tmp);
data[i] = (int16_t)tmp;
}
}
}
#elif defined(USE_SSE2)
#include <emmintrin.h>
#pragma message("Using SSE2 SIMD.")
void vector_add(int16_t* a, int16_t* b, size_t len) {
size_t i = 0;
for (; i + 7 < len; i += 8) {
__m128i va = _mm_loadu_si128((const __m128i*)(a + i));
__m128i vb = _mm_loadu_si128((const __m128i*)(b + i));
__m128i vc = _mm_add_epi16(va, vb);
_mm_storeu_si128((__m128i*)(a + i), vc);
}
for (; i < len; ++i) {
a[i] += b[i];
}
}
void vector_normalize(int16_t* a, size_t len) {
__m128i max_val = _mm_set1_epi16(SMAX);
__m128i min_val = _mm_set1_epi16(SMIN);
size_t i = 0;
for (; i + 7 < len; i += 8) {
__m128i values = _mm_loadu_si128((__m128i*)(a + i));
__m128i gt_max = _mm_cmpgt_epi16(values, max_val);
__m128i lt_min = _mm_cmpgt_epi16(min_val, values);
__m128i max_masked = _mm_and_si128(gt_max, max_val);
__m128i min_masked = _mm_and_si128(lt_min, min_val);
__m128i other_masked = _mm_andnot_si128(_mm_or_si128(gt_max, lt_min), values);
values = _mm_or_si128(_mm_or_si128(max_masked, min_masked), other_masked);
_mm_storeu_si128((__m128i*)(a + i), values);
}
// Process remaining elements
for (; i < len; ++i) {
if (a[i] > SMAX) a[i] = SMAX;
else if (a[i] < SMIN) a[i] = SMIN;
}
}
typedef union {
int16_t* data;
__m128i* fp_sse2;
} vector_data_t;
#else
void vector_add(int16_t* a, int16_t* b, size_t len) {
for (size_t i = 0; i < len; i++) {
a[i] += b[i];
}
}
void vector_normalize(int16_t* a, size_t len) {
for (size_t i = 0; i < len; i++) {
normalize_to_16bit_basic(a[i]);
}
}
void vector_change_sln_volume_granular(int16_t* data, uint32_t samples, int32_t vol) {
float newrate = 0;
static const float pos[GRANULAR_VOLUME_MAX] = {
1.122018, 1.258925, 1.412538, 1.584893, 1.778279, 1.995262, 2.238721, 2.511887, 2.818383, 3.162278,
3.548134, 3.981072, 4.466835, 5.011872, 5.623413, 6.309574, 7.079458, 7.943282, 8.912509, 10.000000,
11.220183, 12.589254, 14.125375, 15.848933, 17.782795, 19.952621, 22.387213, 25.118862, 28.183832, 31.622776,
35.481335, 39.810719, 44.668358, 50.118729, 56.234131, 63.095726, 70.794586, 79.432816, 89.125107, 100.000000,
112.201836, 125.892517, 141.253784, 158.489334, 177.827942, 199.526215, 223.872070, 251.188705, 281.838318, 316.227753
};
static const float neg[GRANULAR_VOLUME_MAX] = {
0.891251, 0.794328, 0.707946, 0.630957, 0.562341, 0.501187, 0.446684, 0.398107, 0.354813, 0.316228,
0.281838, 0.251189, 0.223872, 0.199526, 0.177828, 0.158489, 0.141254, 0.125893, 0.112202, 0.100000,
0.089125, 0.079433, 0.070795, 0.063096, 0.056234, 0.050119, 0.044668, 0.039811, 0.035481, 0.031623,
0.028184, 0.025119, 0.022387, 0.019953, 0.017783, 0.015849, 0.014125, 0.012589, 0.011220, 0.010000,
0.008913, 0.007943, 0.007079, 0.006310, 0.005623, 0.005012, 0.004467, 0.003981, 0.003548, 0.000000 // NOTE mapped -50 dB ratio to total silence instead of 0.003162
};
const float* chart;
uint32_t i;
if (vol == 0) return;
normalize_volume_granular(vol);
chart = vol > 0 ? pos : neg;
i = abs(vol) - 1;
assert(i < GRANULAR_VOLUME_MAX);
newrate = chart[i];
if (newrate) {
int32_t tmp;
uint32_t x;
int16_t *fp = data;
for (x = 0; x < samples; x++) {
tmp = (int32_t) (fp[x] * newrate);
normalize_to_16bit_basic(tmp);
fp[x] = (int16_t) tmp;
}
}
}
#endif
#ifdef __cplusplus
}
#endif
+20
View File
@@ -0,0 +1,20 @@
#ifndef VECTOR_MATH_H
#define VECTOR_MATH_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void vector_add(int16_t* a, int16_t* b, size_t len);
void vector_normalize(int16_t* a, size_t len);
void vector_change_sln_volume_granular(int16_t* data, uint32_t samples, int32_t vol);
#ifdef __cplusplus
}
#endif
#endif
+8
View File
@@ -0,0 +1,8 @@
Copyright 2023, Drachtio Communications Services, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+10
View File
@@ -0,0 +1,10 @@
include $(top_srcdir)/build/modmake.rulesam
MODNAME=mod_azure_tts
mod_LTLIBRARIES = mod_azure_tts.la
mod_azure_tts_la_SOURCES = mod_azure_tts.c azure_glue.cpp
mod_azure_tts_la_CFLAGS = $(AM_CFLAGS)
mod_azure_tts_la_CXXFLAGS = $(AM_CXXFLAGS) -std=c++14 -I/usr/local/include/MicrosoftSpeechSDK/cxx_api -I/usr/local/include/MicrosoftSpeechSDK/c_api
mod_azure_tts_la_LIBADD = $(switch_builddir)/libfreeswitch.la
mod_azure_tts_la_LDFLAGS = -avoid-version -module -no-undefined -L/usr/local/lib/MicrosoftSpeechSDK/x64 -lMicrosoft.CognitiveServices.Speech.core -shared `pkg-config --libs boost` -lstdc++
+380
View File
@@ -0,0 +1,380 @@
#include "mod_azure_tts.h"
#include <switch.h>
#include <speechapi_cxx.h>
#include <boost/circular_buffer.hpp>
#include <cstdlib>
#include <string>
#include <chrono>
#define BUFFER_SIZE 8129
typedef boost::circular_buffer<uint16_t> CircularBuffer_t;
using namespace Microsoft::CognitiveServices::Speech;
static std::string fullDirPath;
extern "C" {
switch_status_t azure_speech_load() {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "azure_speech_loading..\n");
/* 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());
}
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "azure_speech_loaded..\n");
return SWITCH_STATUS_SUCCESS;
}
switch_status_t azure_speech_open(azure_t* azure) {
return SWITCH_STATUS_SUCCESS;
}
switch_status_t azure_speech_feed_tts(azure_t* a, char* text, switch_speech_flag_t *flags) {
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 (a->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);
a->cache_filename = strdup(outfile);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "writing audio cache file to %s\n", a->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 {
a->file = fdopen(fd, "wb");
if (!a->file) {
close(fd);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening cache file %s: %s\n", outfile, strerror(errno));
}
}
}
if (!a->api_key) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "azure_speech_feed_tts: no api_key provided\n");
return SWITCH_STATUS_FALSE;
}
if (!a->language) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "azure_speech_feed_tts: no language provided\n");
return SWITCH_STATUS_FALSE;
}
if (a->session_id) {
int err;
switch_codec_implementation_t read_impl;
switch_core_session_t *psession = switch_core_session_locate(a->session_id);
switch_core_session_get_read_impl(psession, &read_impl);
uint32_t samples_per_second = !strcasecmp(read_impl.iananame, "g722") ? read_impl.actual_samples_per_second : read_impl.samples_per_second;
a->samples_rate = samples_per_second;
if (samples_per_second != 8000 /*Hz*/) {
a->resampler = speex_resampler_init(1, 8000, samples_per_second, SWITCH_RESAMPLE_QUALITY, &err);
if (0 != err) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error initializing resampler: %s.\n", speex_resampler_strerror(err));
return SWITCH_STATUS_FALSE;
}
}
}
std::chrono::time_point<std::chrono::high_resolution_clock>* ptr = new std::chrono::time_point<std::chrono::high_resolution_clock>(std::chrono::high_resolution_clock::now());
a->startTime = ptr;
a->circularBuffer = (void *) new CircularBuffer_t(BUFFER_SIZE);
auto speechConfig = nullptr != a->endpoint ?
(nullptr != a->api_key ?
SpeechConfig::FromEndpoint(a->endpoint, a->api_key) :
SpeechConfig::FromEndpoint(a->endpoint)) :
SpeechConfig::FromSubscription(a->api_key, a->region ? a->region : "");
speechConfig->SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat::Raw8Khz16BitMonoPcm);
speechConfig->SetSpeechSynthesisLanguage(a->language);
speechConfig->SetSpeechSynthesisVoiceName(a->voice_name);
if (a->http_proxy_ip) {
uint32_t port = a->http_proxy_port && a->http_proxy_port[0] != '\0' ? static_cast<uint32_t>(std::stoul(a->http_proxy_port)) : 80;
speechConfig->SetProxy(a->http_proxy_ip, port);
}
if (nullptr != a->endpointId) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts setting endpoint id: %s\n", a->endpointId);
speechConfig->SetEndpointId(a->endpointId);
}
auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
speechSynthesizer->SynthesisStarted += [a](const SpeechSynthesisEventArgs& e) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts SynthesisStarted\n");
a->response_code = 200;
};
speechSynthesizer->Synthesizing += [a](const SpeechSynthesisEventArgs& e) {
bool fireEvent = false;
CircularBuffer_t *cBuffer = (CircularBuffer_t *) a->circularBuffer;
std::vector<uint16_t> pcm_data;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Synthesizing: received data\n");
if (a->flushed) {
return;
}
{
switch_mutex_lock(a->mutex);
auto audioData = e.Result->GetAudioData();
for (size_t i = 0; i < audioData->size(); i += sizeof(int16_t)) {
int16_t value = static_cast<int16_t>((*audioData)[i]) | (static_cast<int16_t>((*audioData)[i + 1]) << 8);
pcm_data.push_back(value);
}
/* and write to the file */
size_t bytesResampled = pcm_data.size() * sizeof(uint16_t);
if (a->file) fwrite(pcm_data.data(), sizeof(uint16_t), pcm_data.size(), a->file);
// Resize the buffer if necessary
if (cBuffer->capacity() - cBuffer->size() < (bytesResampled / sizeof(uint16_t))) {
//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_SIZE));
}
/* Push the data into the buffer */
cBuffer->insert(cBuffer->end(), pcm_data.data(), pcm_data.data() + pcm_data.size());
switch_mutex_unlock(a->mutex);
}
if (0 == a->reads++) {
fireEvent = true;
}
if (fireEvent && a->session_id) {
auto endTime = std::chrono::high_resolution_clock::now();
auto startTime = *static_cast<std::chrono::time_point<std::chrono::high_resolution_clock>*>(a->startTime);
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
auto time_to_first_byte_ms = std::to_string(duration.count());
switch_core_session_t* session = switch_core_session_locate(a->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_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_time_to_first_byte_ms", time_to_first_byte_ms.c_str());
if (a->cache_filename) {
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", a->cache_filename);
}
switch_event_fire(&event);
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "speechSynthesizer->Synthesizing: failed to create event\n");
}
}else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "speechSynthesizer->Synthesizing: channel not found\n");
}
switch_core_session_rwunlock(session);
}
}
};
speechSynthesizer->SynthesisCompleted += [a](const SpeechSynthesisEventArgs& e) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts SynthesisCompleted\n");
a->draining = 1;
};
speechSynthesizer->SynthesisCanceled += [a](const SpeechSynthesisEventArgs& e) {
if (e.Result->Reason == ResultReason::Canceled) {
auto cancellation = SpeechSynthesisCancellationDetails::FromResult(e.Result);
a->response_code = static_cast<long int>(cancellation->ErrorCode);
a->err_msg = strdup(cancellation->ErrorDetails.c_str());
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error synthsize tex %d with error string: %s.\n", static_cast<int>(cancellation->ErrorCode), cancellation->ErrorDetails.c_str());
}
};
// switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts before sending synthesize request\n");
if (std::strncmp(text, "<speak", 6) == 0) {
speechSynthesizer->SpeakSsmlAsync(text);
} else {
speechSynthesizer->SpeakTextAsync(text);
}
// switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts sent synthesize request\n");
return SWITCH_STATUS_SUCCESS;
}
switch_status_t azure_speech_read_tts(azure_t* a, void *data, size_t *datalen, switch_speech_flag_t *flags) {
CircularBuffer_t *cBuffer = (CircularBuffer_t *) a->circularBuffer;
std::vector<uint16_t> pcm_data;
{
switch_mutex_lock(a->mutex);
if (a->response_code > 0 && a->response_code != 200) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "azure_speech_read_tts, returning failure\n") ;
return SWITCH_STATUS_FALSE;
}
if (a->flushed) {
return SWITCH_STATUS_BREAK;
}
if (cBuffer->empty()) {
if (a->draining) {
switch_mutex_unlock(a->mutex);
return SWITCH_STATUS_BREAK;
}
/* no audio available yet so send silence */
memset(data, 255, *datalen);
switch_mutex_unlock(a->mutex);
return SWITCH_STATUS_SUCCESS;
}
// azure returned 8000hz 16 bit data, we have to take enough data based on call sample rate.
size_t size = a->samples_rate ?
std::min((*datalen/(2 * a->samples_rate / 8000)), cBuffer->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(a->mutex);
}
size_t data_size = pcm_data.size();
if (a->resampler) {
std::vector<int16_t> in(pcm_data.begin(), pcm_data.end());
std::vector<int16_t> out((*datalen));
spx_uint32_t in_len = data_size;
spx_uint32_t out_len = out.size();
speex_resampler_process_interleaved_int(a->resampler, in.data(), &in_len, out.data(), &out_len);
if (out_len > out.size()) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Resampler output exceeded maximum buffer size!\n");
return SWITCH_STATUS_FALSE;
}
memcpy(data, out.data(), out_len * sizeof(int16_t));
*datalen = out_len * sizeof(int16_t);
} else {
memcpy(data, pcm_data.data(), data_size * sizeof(int16_t));
*datalen = data_size * sizeof(int16_t);
}
return SWITCH_STATUS_SUCCESS;
}
switch_status_t azure_speech_flush_tts(azure_t* a) {
bool download_complete = a->response_code == 200;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "azure_speech_flush_tts, download complete? %s\n", download_complete ? "yes" : "no") ;
CircularBuffer_t *cBuffer = (CircularBuffer_t *) a->circularBuffer;
delete cBuffer;
a->circularBuffer = nullptr ;
delete static_cast<std::chrono::time_point<std::chrono::high_resolution_clock>*>(a->startTime);
a->startTime = nullptr;
a->flushed = 1;
if (!download_complete) {
if (a->file) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "closing audio cache file %s because download was interrupted\n", a->cache_filename);
if (fclose(a->file) != 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "error closing audio cache file\n");
}
a->file = nullptr ;
}
if (a->cache_filename) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing audio cache file %s because download was interrupted\n", a->cache_filename);
if (unlink(a->cache_filename) != 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cleanupConn: error removing audio cache file %s: %d:%s\n",
a->cache_filename, errno, strerror(errno));
}
free(a->cache_filename);
a->cache_filename = nullptr ;
}
}
if (a->session_id) {
switch_core_session_t* session = switch_core_session_locate(a->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_azure_response_code", std::to_string(a->response_code).c_str());
if (a->cache_filename && download_complete) {
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", a->cache_filename);
}
if (!download_complete && a->err_msg) {
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_error", a->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 azure_speech_close(azure_t* a) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "azure_speech_close\n") ;
if (a->resampler) {
speex_resampler_destroy(a->resampler);
}
a->resampler = NULL;
return SWITCH_STATUS_SUCCESS;
}
switch_status_t azure_speech_unload() {
return SWITCH_STATUS_SUCCESS;
}
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef __AZURE_TTS_GLUE_H__
#define __AZURE_TTS_GLUE_H__
switch_status_t azure_speech_load();
switch_status_t azure_speech_open(azure_t* azure);
switch_status_t azure_speech_feed_tts(azure_t* azure, char* text, switch_speech_flag_t *flags);
switch_status_t azure_speech_read_tts(azure_t* azure, void *data, size_t *datalen, switch_speech_flag_t *flags);
switch_status_t azure_speech_flush_tts(azure_t* azure);
switch_status_t azure_speech_close(azure_t* azure);
switch_status_t azure_speech_unload();
#endif
+178
View File
@@ -0,0 +1,178 @@
#include "mod_azure_tts.h"
#include "azure_glue.h"
SWITCH_MODULE_LOAD_FUNCTION(mod_azure_tts_load);
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_azure_tts_shutdown);
SWITCH_MODULE_DEFINITION(mod_azure_tts, mod_azure_tts_load, mod_azure_tts_shutdown, NULL);
static void clearAzure(azure_t* a, int freeAll) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "clearAzure\n");
if (a->cache_filename) free(a->cache_filename);
if (a->api_key) free(a->api_key);
if (a->language) free(a->language);
if (a->region) free(a->region);
if (a->endpoint) free(a->endpoint);
if (a->endpointId) free(a->endpointId);
if (a->err_msg) free(a->err_msg);
if (a->http_proxy_ip) free(a->http_proxy_ip);
if (a->http_proxy_port) free(a->http_proxy_port);
a->cache_filename = NULL;
a->api_key = NULL;
a->language = NULL;
a->region = NULL;
a->endpoint = NULL;
a->endpointId = NULL;
a->err_msg = NULL;
a->http_proxy_ip = NULL;
a->http_proxy_port = NULL;
if (freeAll) {
if (a->voice_name) free(a->voice_name);
if (a->session_id) free(a->session_id);
a->voice_name = NULL;
a->session_id = NULL;
}
}
static azure_t * createOrRetrievePrivateData(switch_speech_handle_t *sh) {
azure_t *a = (azure_t *) sh->private_info;
if (!a) {
a = switch_core_alloc(sh->memory_pool, sizeof(*a));
sh->private_info = a;
memset(a, 0, sizeof(*a));
switch_mutex_init(&a->mutex, SWITCH_MUTEX_NESTED, sh->memory_pool);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "allocated azure_t\n");
}
return a;
}
switch_status_t a_speech_open(switch_speech_handle_t *sh, const char *voice_name, int rate, int channels, switch_speech_flag_t *flags)
{
azure_t *a = createOrRetrievePrivateData(sh);
a->voice_name = strdup(voice_name);
a->rate = rate;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "a_speech_open voice: %s, rate %d, channels %d\n", voice_name, rate, channels);
return azure_speech_open(a);
}
static switch_status_t a_speech_close(switch_speech_handle_t *sh, switch_speech_flag_t *flags)
{
switch_status_t rc;
azure_t *a = createOrRetrievePrivateData(sh);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "a_speech_close\n");
switch_mutex_destroy(a->mutex);
rc = azure_speech_close(a);
clearAzure(a, 1);
return rc;
}
/**
* Freeswitch will call this function to feed us text to speak
*/
static switch_status_t a_speech_feed_tts(switch_speech_handle_t *sh, char *text, switch_speech_flag_t *flags)
{
azure_t *a = createOrRetrievePrivateData(sh);
a->draining = 0;
a->reads = 0;
a->flushed = 0;
a->samples_rate = 0;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "a_speech_feed_tts\n");
return azure_speech_feed_tts(a, 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 a_speech_read_tts(switch_speech_handle_t *sh, void *data, size_t *datalen, switch_speech_flag_t *flags)
{
azure_t *a = createOrRetrievePrivateData(sh);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "a_speech_read_tts\n");
return azure_speech_read_tts(a, data, datalen, flags);
}
/**
* This is called at the end, not sure exactly what we need to do here..
*/
static void a_speech_flush_tts(switch_speech_handle_t *sh)
{
azure_t *a = createOrRetrievePrivateData(sh);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "w_speech_flush_tts\n");
azure_speech_flush_tts(a);
clearAzure(a, 0);
}
static void a_text_param_tts(switch_speech_handle_t *sh, char *param, const char *val)
{
azure_t *a = createOrRetrievePrivateData(sh);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "a_text_param_tts: %s=%s\n", param, val);
if (0 == strcmp(param, "api_key")) {
if (a->api_key) free(a->api_key);
a->api_key = strdup(val);
} else if (0 == strcmp(param, "region")) {
if (a->region) free(a->region);
a->region = strdup(val);
} else if (0 == strcmp(param, "voice")) {
if (a->voice_name) free(a->voice_name);
a->voice_name = strdup(val);
} else if (0 == strcmp(param, "language")) {
if (a->language) free(a->language);
a->language = strdup(val);
} else if (0 == strcmp(param, "endpoint")) {
if (a->endpoint) free(a->endpoint);
a->endpoint = strdup(val);
} else if (0 == strcmp(param, "endpointId")) {
if (a->endpointId) free(a->endpointId);
a->endpointId = strdup(val);
} else if (0 == strcmp(param, "http_proxy_ip")) {
if (a->http_proxy_ip) free(a->http_proxy_ip);
a->http_proxy_ip = strdup(val);
} else if (0 == strcmp(param, "http_proxy_port")) {
if (a->http_proxy_port) free(a->http_proxy_port);
a->http_proxy_port = strdup(val);
} else if (0 == strcmp(param, "session-uuid")) {
if (a->session_id) free(a->session_id);
a->session_id = strdup(val);
} else if (0 == strcmp(param, "write_cache_file") && switch_true(val)) {
a->cache_audio = 1;
}
}
static void a_numeric_param_tts(switch_speech_handle_t *sh, char *param, int val)
{
}
static void a_float_param_tts(switch_speech_handle_t *sh, char *param, double val)
{
}
SWITCH_MODULE_LOAD_FUNCTION(mod_azure_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 = "microsoft";
speech_interface->speech_open = a_speech_open;
speech_interface->speech_close = a_speech_close;
speech_interface->speech_feed_tts = a_speech_feed_tts;
speech_interface->speech_read_tts = a_speech_read_tts;
speech_interface->speech_flush_tts = a_speech_flush_tts;
speech_interface->speech_text_param_tts = a_text_param_tts;
speech_interface->speech_numeric_param_tts = a_numeric_param_tts;
speech_interface->speech_float_param_tts = a_float_param_tts;
return azure_speech_load();
}
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_azure_tts_shutdown)
{
return azure_speech_unload();
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef __MOD_AZURE_TTS_H__
#define __MOD_AZURE_TTS_H__
#include <switch.h>
#include <speex/speex_resampler.h>
typedef struct azure_data {
char *voice_name;
char *api_key;
char *region;
char *language;
char *endpoint;
char *endpointId;
char *http_proxy_ip;
char *http_proxy_port;
/* result data */
long response_code;
char *session_id;
char *cache_filename;
char *err_msg;
int rate;
int draining;
int reads;
int cache_audio;
int flushed;
uint32_t samples_rate;
void *startTime;
FILE *file;
SpeexResamplerState *resampler;
void *circularBuffer;
switch_mutex_t *mutex;
} azure_t;
#endif
+4 -1
View File
@@ -58,7 +58,7 @@ AudioProducerFile::AudioProducerFile(
std::mutex& mutex, std::mutex& mutex,
CircularBuffer_t& circularBuffer, CircularBuffer_t& circularBuffer,
int sampleRate int sampleRate
) : AudioProducer(mutex, circularBuffer, sampleRate), _timer(io_service), _mh(nullptr) { ) : AudioProducer(mutex, circularBuffer, sampleRate), _timer(io_service), _mh(nullptr), _fp(nullptr) {
AudioProducerFile::_init(); AudioProducerFile::_init();
} }
@@ -176,6 +176,8 @@ void AudioProducerFile::stop() {
} }
void AudioProducerFile::reset() { void AudioProducerFile::reset() {
{
std::lock_guard<std::mutex> lock(_mutex);
if (_fp) { if (_fp) {
fclose(_fp); fclose(_fp);
_fp = nullptr; _fp = nullptr;
@@ -185,6 +187,7 @@ void AudioProducerFile::reset() {
mpg123_delete(_mh); mpg123_delete(_mh);
_mh = nullptr; _mh = nullptr;
} }
}
_timer.cancel(); _timer.cancel();
_status = Status_t::STATUS_NONE; _status = Status_t::STATUS_NONE;
} }
+3
View File
@@ -636,6 +636,8 @@ void AudioProducerHttp::stop() {
} }
void AudioProducerHttp::reset() { void AudioProducerHttp::reset() {
{
std::lock_guard<std::mutex> lock(_mutex);
if (_easy) { if (_easy) {
curl_multi_remove_handle(global.multi, _easy); curl_multi_remove_handle(global.multi, _easy);
curl_easy_cleanup(_easy); curl_easy_cleanup(_easy);
@@ -646,6 +648,7 @@ void AudioProducerHttp::reset() {
mpg123_delete(_mh); mpg123_delete(_mh);
_mh = nullptr; _mh = nullptr;
} }
}
_err_msg.clear(); _err_msg.clear();
_response_code = 0; _response_code = 0;
_timer.cancel(); _timer.cancel();
+16 -2
View File
@@ -26,12 +26,20 @@ void Track::onPlayDone(bool hasError, const std::string& errMsg) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Track::onPlayDone %s\n", _trackName.c_str()); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Track::onPlayDone %s\n", _trackName.c_str());
if (!_stopping) { if (!_stopping) {
std::shared_ptr<AudioProducer> apDone, apNext; // to retain a ref so the ap is not destroyed while under lock below -- avoid deadlock
if (hasError) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "onPlayDone: error: %s\n", errMsg.c_str()); if (hasError) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "onPlayDone: error: %s\n", errMsg.c_str());
{
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
_apQueue.pop();
if (!_apQueue.empty()) { if (!_apQueue.empty()) {
apDone = _apQueue.front();
_apQueue.pop();
}
if (!_apQueue.empty()) apNext = _apQueue.front();
}
if (apNext) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "onPlayDone: starting queued audio on track %s\n", _trackName.c_str()); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "onPlayDone: starting queued audio on track %s\n", _trackName.c_str());
_apQueue.front()->start(std::bind(&Track::onPlayDone, this, std::placeholders::_1, std::placeholders::_2)); apNext->start(std::bind(&Track::onPlayDone, this, std::placeholders::_1, std::placeholders::_2));
} }
} }
else { else {
@@ -50,11 +58,17 @@ void Track::queueFileAudio(const std::string& path, int gain, bool loop) {
_apQueue.push(ap); _apQueue.push(ap);
startIt = _apQueue.size() == 1; startIt = _apQueue.size() == 1;
} }
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
"queueFileAudio: queue file %s on track %s\n", _trackName.c_str(), path.c_str());
if (startIt) { if (startIt) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
"queueFileAudio: track %s starting queued file as none are in progress\n", _trackName.c_str());
try { try {
ap->start(std::bind(&Track::onPlayDone, this, std::placeholders::_1, std::placeholders::_2)); ap->start(std::bind(&Track::onPlayDone, this, std::placeholders::_1, std::placeholders::_2));
} catch (std::exception& e) { } catch (std::exception& e) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR,
"queueFileAudio: track %s error starting %s: %s\n", _trackName.c_str(), path.c_str(), e.what());
onPlayDone(true, e.what()); onPlayDone(true, e.what());
} }
} }
+10
View File
@@ -339,6 +339,16 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
cb->responseHandler(session, "no_audio", cb->bugname); cb->responseHandler(session, "no_audio", cb->bugname);
} }
} }
else {
cJSON* json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "type", "error");
cJSON_AddItemToObject(json, "error_code", cJSON_CreateNumber(status.error_code()));
cJSON_AddStringToObject(json, "error_message", status.error_message().c_str());
char* jsonString = cJSON_PrintUnformatted(json);
cb->responseHandler(session, jsonString, cb->bugname);
free(jsonString);
cJSON_Delete(json);
}
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_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_core_session_rwunlock(session);
} }
+10
View File
@@ -296,6 +296,16 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
cb->responseHandler(session, "no_audio", cb->bugname); cb->responseHandler(session, "no_audio", cb->bugname);
} }
} }
else {
cJSON* json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "type", "error");
cJSON_AddItemToObject(json, "error_code", cJSON_CreateNumber(status.error_code()));
cJSON_AddStringToObject(json, "error_message", status.error_message().c_str());
char* jsonString = cJSON_PrintUnformatted(json);
cb->responseHandler(session, jsonString, cb->bugname);
free(jsonString);
cJSON_Delete(json);
}
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_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_core_session_rwunlock(session);
} }