mirror of
https://github.com/jambonz/freeswitch-modules.git
synced 2026-01-25 02:08:27 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83a3f71ce7 | |||
| 3aa81cc846 | |||
| 3beebf3471 | |||
| c866e81a23 | |||
| a9e630a335 | |||
| a5517cff70 | |||
| 86d07a582b |
@@ -480,7 +480,7 @@ AudioPipe::AudioPipe(const char* uuid, const char* host, unsigned int port, cons
|
|||||||
}
|
}
|
||||||
AudioPipe::~AudioPipe() {
|
AudioPipe::~AudioPipe() {
|
||||||
if (m_audio_buffer) delete [] m_audio_buffer;
|
if (m_audio_buffer) delete [] m_audio_buffer;
|
||||||
if (m_recv_buf) free(m_recv_buf);
|
if (m_recv_buf) delete [] m_recv_buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioPipe::connect(void) {
|
void AudioPipe::connect(void) {
|
||||||
|
|||||||
@@ -173,12 +173,13 @@ public:
|
|||||||
char *metadata,
|
char *metadata,
|
||||||
const char* awsAccessKeyId,
|
const char* awsAccessKeyId,
|
||||||
const char* awsSecretAccessKey,
|
const char* awsSecretAccessKey,
|
||||||
const char* awsSessionToken,
|
|
||||||
responseHandler_t responseHandler,
|
responseHandler_t responseHandler,
|
||||||
errorHandler_t errorHandler) :
|
errorHandler_t errorHandler) :
|
||||||
m_bot(bot), m_alias(alias), m_region(region), m_sessionId(sessionId), m_finished(false), m_finishing(false), m_packets(0),
|
m_bot(bot), m_alias(alias), m_region(region), m_sessionId(sessionId), m_finished(false), m_finishing(false), m_packets(0),
|
||||||
m_pStream(nullptr), m_bPlayDone(false), m_bDiscardAudio(false)
|
m_pStream(nullptr), m_bPlayDone(false), m_bDiscardAudio(false)
|
||||||
{
|
{
|
||||||
|
Aws::String key(awsAccessKeyId);
|
||||||
|
Aws::String secret(awsSecretAccessKey);
|
||||||
Aws::String awsLocale(locale);
|
Aws::String awsLocale(locale);
|
||||||
Aws::Client::ClientConfiguration config;
|
Aws::Client::ClientConfiguration config;
|
||||||
config.region = region;
|
config.region = region;
|
||||||
@@ -190,10 +191,7 @@ public:
|
|||||||
keySnippet[19] = '\0';
|
keySnippet[19] = '\0';
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p ACCESS_KEY_ID %s\n", this, keySnippet);
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p ACCESS_KEY_ID %s\n", this, keySnippet);
|
||||||
if (*awsAccessKeyId && *awsSecretAccessKey && *awsSessionToken) {
|
if (*awsAccessKeyId && *awsSecretAccessKey) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "using AWS creds %s %s %s\n", awsAccessKeyId, awsSecretAccessKey, awsSessionToken);
|
|
||||||
m_client = Aws::MakeUnique<LexRuntimeV2Client>(ALLOC_TAG, AWSCredentials(awsAccessKeyId, awsSecretAccessKey, awsSessionToken), config);
|
|
||||||
} else if (*awsAccessKeyId && *awsSecretAccessKey) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "using AWS creds %s %s\n", awsAccessKeyId, awsSecretAccessKey);
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "using AWS creds %s %s\n", awsAccessKeyId, awsSecretAccessKey);
|
||||||
m_client = Aws::MakeUnique<LexRuntimeV2Client>(ALLOC_TAG, AWSCredentials(awsAccessKeyId, awsSecretAccessKey), config);
|
m_client = Aws::MakeUnique<LexRuntimeV2Client>(ALLOC_TAG, AWSCredentials(awsAccessKeyId, awsSecretAccessKey), config);
|
||||||
}
|
}
|
||||||
@@ -542,7 +540,7 @@ static void *SWITCH_THREAD_FUNC lex_thread(switch_thread_t *thread, void *obj) {
|
|||||||
bool ok = true;
|
bool ok = true;
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "lex_thread: starting cb %p\n", (void *) cb);
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "lex_thread: starting cb %p\n", (void *) cb);
|
||||||
GStreamer* pStreamer = new GStreamer(cb->sessionId, cb->bot, cb->alias, cb->region, cb->locale,
|
GStreamer* pStreamer = new GStreamer(cb->sessionId, cb->bot, cb->alias, cb->region, cb->locale,
|
||||||
cb->intent, cb->metadata, cb->awsAccessKeyId, cb->awsSecretAccessKey, cb->awsSessionToken,
|
cb->intent, cb->metadata, cb->awsAccessKeyId, cb->awsSecretAccessKey,
|
||||||
cb->responseHandler, cb->errorHandler);
|
cb->responseHandler, cb->errorHandler);
|
||||||
if (!pStreamer) {
|
if (!pStreamer) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "lex_thread: Error allocating streamer\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "lex_thread: Error allocating streamer\n");
|
||||||
@@ -643,7 +641,6 @@ extern "C" {
|
|||||||
memset(cb, sizeof(cb), 0);
|
memset(cb, sizeof(cb), 0);
|
||||||
const char* awsAccessKeyId = switch_channel_get_variable(channel, "AWS_ACCESS_KEY_ID");
|
const char* awsAccessKeyId = switch_channel_get_variable(channel, "AWS_ACCESS_KEY_ID");
|
||||||
const char* awsSecretAccessKey = switch_channel_get_variable(channel, "AWS_SECRET_ACCESS_KEY");
|
const char* awsSecretAccessKey = switch_channel_get_variable(channel, "AWS_SECRET_ACCESS_KEY");
|
||||||
const char* awsSessionToken = switch_channel_get_variable(channel, "AWS_SESSION_TOKEN");
|
|
||||||
|
|
||||||
if (!hasDefaultCredentials && (!awsAccessKeyId || !awsSecretAccessKey)) {
|
if (!hasDefaultCredentials && (!awsAccessKeyId || !awsSecretAccessKey)) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR,
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR,
|
||||||
@@ -657,7 +654,6 @@ extern "C" {
|
|||||||
if (awsAccessKeyId && awsSecretAccessKey) {
|
if (awsAccessKeyId && awsSecretAccessKey) {
|
||||||
strncpy(cb->awsAccessKeyId, awsAccessKeyId, 128);
|
strncpy(cb->awsAccessKeyId, awsAccessKeyId, 128);
|
||||||
strncpy(cb->awsSecretAccessKey, awsSecretAccessKey, 128);
|
strncpy(cb->awsSecretAccessKey, awsSecretAccessKey, 128);
|
||||||
if (awsSessionToken) strncpy(cb->awsSessionToken, awsSessionToken, 1024);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
strncpy(cb->awsAccessKeyId, std::getenv("AWS_ACCESS_KEY_ID"), 128);
|
strncpy(cb->awsAccessKeyId, std::getenv("AWS_ACCESS_KEY_ID"), 128);
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ struct cap_cb {
|
|||||||
char sessionId[256];
|
char sessionId[256];
|
||||||
char awsAccessKeyId[128];
|
char awsAccessKeyId[128];
|
||||||
char awsSecretAccessKey[128];
|
char awsSecretAccessKey[128];
|
||||||
char awsSessionToken[1024];
|
|
||||||
SpeexResamplerState *resampler;
|
SpeexResamplerState *resampler;
|
||||||
void* streamer;
|
void* streamer;
|
||||||
responseHandler_t responseHandler;
|
responseHandler_t responseHandler;
|
||||||
|
|||||||
@@ -49,11 +49,12 @@ public:
|
|||||||
const char* region,
|
const char* region,
|
||||||
const char* awsAccessKeyId,
|
const char* awsAccessKeyId,
|
||||||
const char* awsSecretAccessKey,
|
const char* awsSecretAccessKey,
|
||||||
const char* awsSessionToken,
|
|
||||||
responseHandler_t responseHandler
|
responseHandler_t responseHandler
|
||||||
) : m_sessionId(sessionId), m_bugname(bugname), m_finished(false), m_interim(interim), m_finishing(false), m_connected(false), m_connecting(false),
|
) : m_sessionId(sessionId), m_bugname(bugname), m_finished(false), m_interim(interim), m_finishing(false), m_connected(false), m_connecting(false),
|
||||||
m_packets(0), m_responseHandler(responseHandler), m_pStream(nullptr),
|
m_packets(0), m_responseHandler(responseHandler), m_pStream(nullptr),
|
||||||
m_audioBuffer(320 * (samples_per_second == 8000 ? 1 : 2), 15) {
|
m_audioBuffer(320 * (samples_per_second == 8000 ? 1 : 2), 15) {
|
||||||
|
Aws::String key(awsAccessKeyId);
|
||||||
|
Aws::String secret(awsSecretAccessKey);
|
||||||
Aws::Client::ClientConfiguration config;
|
Aws::Client::ClientConfiguration config;
|
||||||
if (region != nullptr && strlen(region) > 0) config.region = region;
|
if (region != nullptr && strlen(region) > 0) config.region = region;
|
||||||
char keySnippet[20];
|
char keySnippet[20];
|
||||||
@@ -63,9 +64,7 @@ public:
|
|||||||
keySnippet[19] = '\0';
|
keySnippet[19] = '\0';
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p ACCESS_KEY_ID %s, region %s\n", this, keySnippet, region);
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "GStreamer %p ACCESS_KEY_ID %s, region %s\n", this, keySnippet, region);
|
||||||
if (*awsAccessKeyId && *awsSecretAccessKey && *awsSessionToken) {
|
if (*awsAccessKeyId && *awsSecretAccessKey) {
|
||||||
m_client = Aws::MakeUnique<TranscribeStreamingServiceClient>(ALLOC_TAG, AWSCredentials(awsAccessKeyId, awsSecretAccessKey, awsSessionToken), config);
|
|
||||||
} else if (*awsAccessKeyId && *awsSecretAccessKey) {
|
|
||||||
m_client = Aws::MakeUnique<TranscribeStreamingServiceClient>(ALLOC_TAG, AWSCredentials(awsAccessKeyId, awsSecretAccessKey), config);
|
m_client = Aws::MakeUnique<TranscribeStreamingServiceClient>(ALLOC_TAG, AWSCredentials(awsAccessKeyId, awsSecretAccessKey), config);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -321,8 +320,8 @@ static void *SWITCH_THREAD_FUNC aws_transcribe_thread(switch_thread_t *thread, v
|
|||||||
struct cap_cb *cb = (struct cap_cb *) obj;
|
struct cap_cb *cb = (struct cap_cb *) obj;
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "transcribe_thread: starting cb %p\n", (void *) cb);
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "transcribe_thread: starting cb %p\n", (void *) cb);
|
||||||
GStreamer* pStreamer = new GStreamer(cb->sessionId, cb->bugname, cb->channels, cb->lang, cb->interim, cb->samples_per_second,
|
GStreamer* pStreamer = new GStreamer(cb->sessionId, cb->bugname, cb->channels, cb->lang, cb->interim, cb->samples_per_second, cb->region, cb->awsAccessKeyId, cb->awsSecretAccessKey,
|
||||||
cb->region, cb->awsAccessKeyId, cb->awsSecretAccessKey, cb->awsSessionToken, cb->responseHandler);
|
cb->responseHandler);
|
||||||
if (!pStreamer) {
|
if (!pStreamer) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "transcribe_thread: Error allocating streamer\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "transcribe_thread: Error allocating streamer\n");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -409,7 +408,6 @@ extern "C" {
|
|||||||
memset(cb, sizeof(cb), 0);
|
memset(cb, sizeof(cb), 0);
|
||||||
const char* awsAccessKeyId = switch_channel_get_variable(channel, "AWS_ACCESS_KEY_ID");
|
const char* awsAccessKeyId = switch_channel_get_variable(channel, "AWS_ACCESS_KEY_ID");
|
||||||
const char* awsSecretAccessKey = switch_channel_get_variable(channel, "AWS_SECRET_ACCESS_KEY");
|
const char* awsSecretAccessKey = switch_channel_get_variable(channel, "AWS_SECRET_ACCESS_KEY");
|
||||||
const char* awsSessionToken = switch_channel_get_variable(channel, "AWS_SESSION_TOKEN");
|
|
||||||
const char* awsRegion = switch_channel_get_variable(channel, "AWS_REGION");
|
const char* awsRegion = switch_channel_get_variable(channel, "AWS_REGION");
|
||||||
cb->channels = channels;
|
cb->channels = channels;
|
||||||
LanguageCode code = LanguageCodeMapper::GetLanguageCodeForName(lang);
|
LanguageCode code = LanguageCodeMapper::GetLanguageCodeForName(lang);
|
||||||
@@ -421,12 +419,12 @@ extern "C" {
|
|||||||
strncpy(cb->sessionId, switch_core_session_get_uuid(session), MAX_SESSION_ID);
|
strncpy(cb->sessionId, switch_core_session_get_uuid(session), MAX_SESSION_ID);
|
||||||
strncpy(cb->bugname, bugname, MAX_BUG_LEN);
|
strncpy(cb->bugname, bugname, MAX_BUG_LEN);
|
||||||
|
|
||||||
if (awsRegion) strncpy(cb->region, awsRegion, MAX_REGION);
|
if (awsAccessKeyId && awsSecretAccessKey && awsRegion) {
|
||||||
if (awsAccessKeyId && awsSecretAccessKey) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Using channel vars for aws authentication\n");
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Using channel vars for aws authentication\n");
|
||||||
strncpy(cb->awsAccessKeyId, awsAccessKeyId, 128);
|
strncpy(cb->awsAccessKeyId, awsAccessKeyId, 128);
|
||||||
strncpy(cb->awsSecretAccessKey, awsSecretAccessKey, 128);
|
strncpy(cb->awsSecretAccessKey, awsSecretAccessKey, 128);
|
||||||
if (awsSessionToken) strncpy(cb->awsSessionToken, awsSessionToken, 1024);
|
strncpy(cb->region, awsRegion, MAX_REGION);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (std::getenv("AWS_ACCESS_KEY_ID") &&
|
else if (std::getenv("AWS_ACCESS_KEY_ID") &&
|
||||||
std::getenv("AWS_SECRET_ACCESS_KEY") &&
|
std::getenv("AWS_SECRET_ACCESS_KEY") &&
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ struct cap_cb {
|
|||||||
char sessionId[MAX_SESSION_ID+1];
|
char sessionId[MAX_SESSION_ID+1];
|
||||||
char awsAccessKeyId[128];
|
char awsAccessKeyId[128];
|
||||||
char awsSecretAccessKey[128];
|
char awsSecretAccessKey[128];
|
||||||
char awsSessionToken[1024];
|
|
||||||
uint32_t channels;
|
uint32_t channels;
|
||||||
SpeexResamplerState *resampler;
|
SpeexResamplerState *resampler;
|
||||||
void* streamer;
|
void* streamer;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ extern "C" {
|
|||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullDirPath = std::string(baseDir) + "tts-cache-files";
|
fullDirPath = std::string(baseDir) + "jambonz-tts-cache-files";
|
||||||
|
|
||||||
// Create the directory with read, write, and execute permissions for everyone
|
// Create the directory with read, write, and execute permissions for everyone
|
||||||
mode_t oldMask = umask(0);
|
mode_t oldMask = umask(0);
|
||||||
@@ -135,12 +135,23 @@ extern "C" {
|
|||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a->rate != 8000 /*Hz*/) {
|
if (a->session_id) {
|
||||||
int err;
|
int err;
|
||||||
a->resampler = speex_resampler_init(1, 8000, a->rate, SWITCH_RESAMPLE_QUALITY, &err);
|
switch_codec_implementation_t read_impl;
|
||||||
if (0 != err) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error initializing resampler: %s.\n", speex_resampler_strerror(err));
|
/* lock and unlock session */
|
||||||
return SWITCH_STATUS_FALSE;
|
switch_core_session_t *psession = switch_core_session_locate(a->session_id);
|
||||||
|
switch_core_session_get_read_impl(psession, &read_impl);
|
||||||
|
switch_core_session_rwunlock(psession);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,77 +179,72 @@ extern "C" {
|
|||||||
speechConfig->SetEndpointId(a->endpointId);
|
speechConfig->SetEndpointId(a->endpointId);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
|
||||||
auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
|
|
||||||
|
|
||||||
speechSynthesizer->SynthesisStarted += [a](const SpeechSynthesisEventArgs& e) {
|
speechSynthesizer->SynthesisStarted += [a](const SpeechSynthesisEventArgs& e) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts SynthesisStarted\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts SynthesisStarted\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
speechSynthesizer->Synthesizing += [a](const SpeechSynthesisEventArgs& e) {
|
speechSynthesizer->Synthesizing += [a](const SpeechSynthesisEventArgs& e) {
|
||||||
if (a->flushed) return;
|
if (a->flushed) return;
|
||||||
bool fireEvent = false;
|
bool fireEvent = false;
|
||||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) a->circularBuffer;
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) a->circularBuffer;
|
||||||
|
|
||||||
auto audioData = e.Result->GetAudioData();
|
auto audioData = e.Result->GetAudioData();
|
||||||
if (a->file) {
|
if (a->file) {
|
||||||
fwrite(audioData->data(), 1, audioData->size(), a->file);
|
fwrite(audioData->data(), 1, audioData->size(), a->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this sort of reinterpretation can be dangerous as a general rule, but in this case we know that the data
|
* this sort of reinterpretation can be dangerous as a general rule, but in this case we know that the data
|
||||||
* is 16-bit PCM, so it's safe to do this and its much faster than copying the data byte by byte
|
* is 16-bit PCM, so it's safe to do this and its much faster than copying the data byte by byte
|
||||||
*/
|
*/
|
||||||
const uint16_t* begin = reinterpret_cast<const uint16_t*>(audioData->data());
|
const uint16_t* begin = reinterpret_cast<const uint16_t*>(audioData->data());
|
||||||
const uint16_t* end = reinterpret_cast<const uint16_t*>(audioData->data() + audioData->size());
|
const uint16_t* end = reinterpret_cast<const uint16_t*>(audioData->data() + audioData->size());
|
||||||
|
|
||||||
/* lock as briefly as possible */
|
/* lock as briefly as possible */
|
||||||
switch_mutex_lock(a->mutex);
|
switch_mutex_lock(a->mutex);
|
||||||
if (cBuffer->capacity() - cBuffer->size() < audioData->size()) {
|
if (cBuffer->capacity() - cBuffer->size() < audioData->size()) {
|
||||||
cBuffer->set_capacity(cBuffer->size() + std::max( audioData->size(), (size_t)BUFFER_SIZE));
|
cBuffer->set_capacity(cBuffer->size() + std::max( audioData->size(), (size_t)BUFFER_SIZE));
|
||||||
}
|
}
|
||||||
cBuffer->insert(cBuffer->end(), begin, end);
|
cBuffer->insert(cBuffer->end(), begin, end);
|
||||||
switch_mutex_unlock(a->mutex);
|
switch_mutex_unlock(a->mutex);
|
||||||
|
|
||||||
if (0 == a->reads++) {
|
if (0 == a->reads++) {
|
||||||
fireEvent = true;
|
fireEvent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fireEvent && a->session_id) {
|
if (fireEvent && a->session_id) {
|
||||||
auto endTime = std::chrono::high_resolution_clock::now();
|
auto endTime = std::chrono::high_resolution_clock::now();
|
||||||
auto startTime = *static_cast<std::chrono::time_point<std::chrono::high_resolution_clock>*>(a->startTime);
|
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 duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
|
||||||
auto time_to_first_byte_ms = std::to_string(duration.count());
|
auto time_to_first_byte_ms = std::to_string(duration.count());
|
||||||
switch_core_session_t* session = switch_core_session_locate(a->session_id);
|
switch_core_session_t* session = switch_core_session_locate(a->session_id);
|
||||||
if (session) {
|
if (session) {
|
||||||
switch_channel_t *channel = switch_core_session_get_channel(session);
|
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||||
switch_core_session_rwunlock(session);
|
switch_core_session_rwunlock(session);
|
||||||
if (channel) {
|
if (channel) {
|
||||||
switch_event_t *event;
|
switch_event_t *event;
|
||||||
if (switch_event_create(&event, SWITCH_EVENT_PLAYBACK_START) == SWITCH_STATUS_SUCCESS) {
|
if (switch_event_create(&event, SWITCH_EVENT_PLAYBACK_START) == SWITCH_STATUS_SUCCESS) {
|
||||||
switch_channel_event_set_data(channel, event);
|
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, "Playback-File-Type", "tts_stream");
|
||||||
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_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) {
|
if (a->cache_filename) {
|
||||||
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", 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_event_fire(&event);
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "speechSynthesizer->Synthesizing: channel not found\n");
|
} 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
std::thread(start_synthesis, speechSynthesizer, text, a).detach();
|
std::thread(start_synthesis, speechSynthesizer, text, a).detach();
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts sent synthesize request\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "azure_speech_feed_tts sent synthesize request\n");
|
||||||
} catch (const std::exception& e) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mod_azure_tts: Exception: %s\n", e.what());
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +271,9 @@ extern "C" {
|
|||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
// azure returned 8000hz 16 bit data, we have to take enough data based on call sample rate.
|
// azure returned 8000hz 16 bit data, we have to take enough data based on call sample rate.
|
||||||
size_t size = std::min((*datalen/(2 * a->rate / 8000)), bufSize);
|
size_t size = a->samples_rate ?
|
||||||
|
std::min((*datalen/(2 * a->samples_rate / 8000)), bufSize) :
|
||||||
|
std::min((*datalen/2), bufSize);
|
||||||
pcm_data.insert(pcm_data.end(), cBuffer->begin(), cBuffer->begin() + size);
|
pcm_data.insert(pcm_data.end(), cBuffer->begin(), cBuffer->begin() + size);
|
||||||
cBuffer->erase(cBuffer->begin(), cBuffer->begin() + size);
|
cBuffer->erase(cBuffer->begin(), cBuffer->begin() + size);
|
||||||
switch_mutex_unlock(a->mutex);
|
switch_mutex_unlock(a->mutex);
|
||||||
|
|||||||
@@ -82,8 +82,7 @@ static switch_status_t a_speech_feed_tts(switch_speech_handle_t *sh, char *text,
|
|||||||
a->draining = 0;
|
a->draining = 0;
|
||||||
a->reads = 0;
|
a->reads = 0;
|
||||||
a->flushed = 0;
|
a->flushed = 0;
|
||||||
a->response_code = 0;
|
a->samples_rate = 0;
|
||||||
a->err_msg = NULL;
|
|
||||||
|
|
||||||
return azure_speech_feed_tts(a, text, flags);
|
return azure_speech_feed_tts(a, text, flags);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ typedef struct azure_data {
|
|||||||
int reads;
|
int reads;
|
||||||
int cache_audio;
|
int cache_audio;
|
||||||
int flushed;
|
int flushed;
|
||||||
|
uint32_t samples_rate;
|
||||||
|
|
||||||
void *startTime;
|
void *startTime;
|
||||||
|
|
||||||
|
|||||||
@@ -176,9 +176,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);
|
||||||
if (!ap->m_silence_disconnect) {
|
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(), ap->m_bugname.c_str(), deepgram::AudioPipe::MESSAGE, msg.c_str(), ap->isFinished());
|
|
||||||
}
|
|
||||||
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;
|
||||||
@@ -453,7 +451,7 @@ AudioPipe::AudioPipe(const char* uuid, const char* bugname, const char* host, un
|
|||||||
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_bugname(bugname),
|
||||||
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_useTls(useTls),
|
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), m_silence_disconnect(false) {
|
m_state(LWS_CLIENT_IDLE), m_wsi(nullptr), m_vhd(nullptr), m_callback(callback) {
|
||||||
|
|
||||||
if (apiKey) m_apiKey = apiKey;
|
if (apiKey) m_apiKey = apiKey;
|
||||||
else m_apiKey = "";
|
else m_apiKey = "";
|
||||||
@@ -518,11 +516,6 @@ void AudioPipe::finish() {
|
|||||||
bufferForSending("{\"type\": \"CloseStream\"}");
|
bufferForSending("{\"type\": \"CloseStream\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioPipe::finish_in_silence() {
|
|
||||||
m_silence_disconnect = true;
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioPipe::waitForClose() {
|
void AudioPipe::waitForClose() {
|
||||||
std::shared_future<void> sf(m_promise.get_future());
|
std::shared_future<void> sf(m_promise.get_future());
|
||||||
sf.wait();
|
sf.wait();
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ namespace deepgram {
|
|||||||
|
|
||||||
void close() ;
|
void close() ;
|
||||||
void finish();
|
void finish();
|
||||||
void finish_in_silence();
|
|
||||||
void waitForClose();
|
void waitForClose();
|
||||||
void setClosed() { m_promise.set_value(); }
|
void setClosed() { m_promise.set_value(); }
|
||||||
bool isFinished() { return m_finished;}
|
bool isFinished() { return m_finished;}
|
||||||
@@ -139,7 +138,6 @@ namespace deepgram {
|
|||||||
std::string m_bugname;
|
std::string m_bugname;
|
||||||
std::promise<void> m_promise;
|
std::promise<void> m_promise;
|
||||||
bool m_useTls;
|
bool m_useTls;
|
||||||
bool m_silence_disconnect;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace deepgram
|
} // namespace deepgram
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
#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 DEEPGRAM_KEEP_ALIVE_INTERVAL_SECOND 8
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
static bool hasDefaultCredentials = false;
|
static bool hasDefaultCredentials = false;
|
||||||
@@ -87,17 +86,13 @@ namespace {
|
|||||||
static const char* emptyTranscript =
|
static const char* emptyTranscript =
|
||||||
"\"is_final\":false,\"speech_final\":false,\"channel\":{\"alternatives\":[{\"transcript\":\"\",\"confidence\":0.0,\"words\":[]}]}";
|
"\"is_final\":false,\"speech_final\":false,\"channel\":{\"alternatives\":[{\"transcript\":\"\",\"confidence\":0.0,\"words\":[]}]}";
|
||||||
|
|
||||||
static void reaper(private_t *tech_pvt, bool silence_disconnect) {
|
static void reaper(private_t *tech_pvt) {
|
||||||
std::shared_ptr<deepgram::AudioPipe> pAp;
|
std::shared_ptr<deepgram::AudioPipe> pAp;
|
||||||
pAp.reset((deepgram::AudioPipe *)tech_pvt->pAudioPipe);
|
pAp.reset((deepgram::AudioPipe *)tech_pvt->pAudioPipe);
|
||||||
tech_pvt->pAudioPipe = nullptr;
|
tech_pvt->pAudioPipe = nullptr;
|
||||||
|
|
||||||
std::thread t([pAp, tech_pvt, silence_disconnect]{
|
std::thread t([pAp, tech_pvt]{
|
||||||
if (silence_disconnect) {
|
pAp->finish();
|
||||||
pAp->finish_in_silence();
|
|
||||||
} else {
|
|
||||||
pAp->finish();
|
|
||||||
}
|
|
||||||
pAp->waitForClose();
|
pAp->waitForClose();
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%u) got remote close\n", tech_pvt->sessionId, tech_pvt->id);
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%u) got remote close\n", tech_pvt->sessionId, tech_pvt->id);
|
||||||
});
|
});
|
||||||
@@ -347,83 +342,61 @@ namespace {
|
|||||||
|
|
||||||
int err;
|
int err;
|
||||||
int useTls = true;
|
int useTls = true;
|
||||||
std::string host;
|
|
||||||
int port = 443;
|
|
||||||
size_t buflen = LWS_PRE + (FRAME_SIZE_8000 * desiredSampling / 8000 * channels * 1000 / RTP_PACKETIZATION_PERIOD * nAudioBufferSecs);
|
|
||||||
|
|
||||||
std::ostringstream configuration_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);
|
||||||
|
|
||||||
switch_core_session_get_read_impl(session, &read_impl);
|
switch_core_session_get_read_impl(session, &read_impl);
|
||||||
|
|
||||||
|
memset(tech_pvt, 0, sizeof(private_t));
|
||||||
|
|
||||||
std::string path;
|
std::string path;
|
||||||
constructPath(session, path, desiredSampling, channels, lang, interim);
|
constructPath(session, path, desiredSampling, channels, lang, interim);
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "path: %s\n", path.c_str());
|
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");
|
const char* endpoint = switch_channel_get_variable(channel, "DEEPGRAM_URI");
|
||||||
if (endpoint != nullptr) {
|
if (endpoint != nullptr) {
|
||||||
std::string ep(endpoint);
|
std::string ep(endpoint);
|
||||||
|
|
||||||
useTls = switch_true(switch_channel_get_variable(channel, "DEEPGRAM_USE_TLS"));
|
useTls = switch_true(switch_channel_get_variable(channel, "DEEPGRAM_USE_TLS"));
|
||||||
|
|
||||||
size_t pos = ep.find(':');
|
size_t pos = ep.find(':');
|
||||||
host = ep;
|
|
||||||
if (pos != std::string::npos) {
|
if (pos != std::string::npos) {
|
||||||
host = ep.substr(0, pos);
|
std::string host = ep.substr(0, pos);
|
||||||
std::string strPort = ep.substr(pos + 1);
|
std::string port = ep.substr(pos + 1);
|
||||||
port = ::atoi(strPort.c_str());
|
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,
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO,
|
||||||
"connecting to deepgram on-prem %s port %d, using tls? (%s)\n", host.c_str(), port, useTls ? "yes" : "no");
|
"connecting to deepgram on-prem %s port %d, using tls? (%s)\n", tech_pvt->host, tech_pvt->port, useTls ? "yes" : "no");
|
||||||
} else {
|
}
|
||||||
host = "api.deepgram.com";
|
else {
|
||||||
|
strncpy(tech_pvt->host, "api.deepgram.com", MAX_WS_URL_LEN);
|
||||||
|
tech_pvt->port = 443;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* apiKey = switch_channel_get_variable(channel, "DEEPGRAM_API_KEY");
|
|
||||||
if (!apiKey && defaultApiKey) {
|
|
||||||
apiKey = defaultApiKey;
|
|
||||||
} else if (!apiKey && endpoint == nullptr) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "no deepgram api key provided\n");
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration_stream <<
|
|
||||||
host << ":" <<
|
|
||||||
port << ";" <<
|
|
||||||
path << ";" <<
|
|
||||||
buflen << ";" <<
|
|
||||||
read_impl.decoded_bytes_per_packet << ";" <<
|
|
||||||
apiKey << ";" <<
|
|
||||||
useTls;
|
|
||||||
|
|
||||||
if (tech_pvt->pAudioPipe) {
|
|
||||||
// stop sending keep alive
|
|
||||||
tech_pvt->is_keep_alive = 0;
|
|
||||||
if (0 != strcmp(tech_pvt->configuration, configuration_stream.str().c_str())) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "fork_data_init: stop existing deepgram connection, old configuration %s, new configuration %s\n",
|
|
||||||
tech_pvt->configuration, configuration_stream.str().c_str());
|
|
||||||
reaper(tech_pvt, true);
|
|
||||||
} else {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "fork_data_init: enable existing deepgram connection\n");
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
memset(tech_pvt, 0, sizeof(private_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(tech_pvt->sessionId, switch_core_session_get_uuid(session), MAX_SESSION_ID);
|
|
||||||
strncpy(tech_pvt->host, host.c_str(), MAX_WS_URL_LEN);
|
|
||||||
tech_pvt->port = port;
|
|
||||||
strncpy(tech_pvt->path, path.c_str(), MAX_PATH_LEN);
|
strncpy(tech_pvt->path, path.c_str(), MAX_PATH_LEN);
|
||||||
strncpy(tech_pvt->configuration, configuration_stream.str().c_str(), MAX_PATH_LEN) ;
|
|
||||||
tech_pvt->sampling = desiredSampling;
|
tech_pvt->sampling = desiredSampling;
|
||||||
tech_pvt->responseHandler = responseHandler;
|
tech_pvt->responseHandler = responseHandler;
|
||||||
tech_pvt->channels = channels;
|
tech_pvt->channels = channels;
|
||||||
tech_pvt->id = ++idxCallCount;
|
tech_pvt->id = ++idxCallCount;
|
||||||
tech_pvt->buffer_overrun_notified = 0;
|
tech_pvt->buffer_overrun_notified = 0;
|
||||||
tech_pvt->is_keep_alive = 0;
|
|
||||||
strncpy(tech_pvt->bugname, bugname, MAX_BUG_LEN);
|
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) {
|
||||||
|
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,
|
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);
|
buflen, read_impl.decoded_bytes_per_packet, apiKey, useTls, eventCallback);
|
||||||
if (!ap) {
|
if (!ap) {
|
||||||
@@ -433,26 +406,18 @@ namespace {
|
|||||||
|
|
||||||
tech_pvt->pAudioPipe = static_cast<void *>(ap);
|
tech_pvt->pAudioPipe = static_cast<void *>(ap);
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connecting now\n");
|
switch_mutex_init(&tech_pvt->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
|
||||||
ap->connect();
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connection in progress\n");
|
|
||||||
|
|
||||||
if (!tech_pvt->mutex) {
|
if (desiredSampling != sampling) {
|
||||||
switch_mutex_init(&tech_pvt->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) resampling from %u to %u\n", tech_pvt->id, sampling, desiredSampling);
|
||||||
|
tech_pvt->resampler = speex_resampler_init(channels, sampling, desiredSampling, SWITCH_RESAMPLE_QUALITY, &err);
|
||||||
|
if (0 != err) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error initializing resampler: %s.\n", speex_resampler_strerror(err));
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if (!tech_pvt->resampler) {
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) no resampling needed for this call\n", tech_pvt->id);
|
||||||
if (desiredSampling != sampling) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) resampling from %u to %u\n", tech_pvt->id, sampling, desiredSampling);
|
|
||||||
tech_pvt->resampler = speex_resampler_init(channels, sampling, desiredSampling, SWITCH_RESAMPLE_QUALITY, &err);
|
|
||||||
if (0 != err) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error initializing resampler: %s.\n", speex_resampler_strerror(err));
|
|
||||||
return SWITCH_STATUS_FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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) 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);
|
||||||
@@ -511,20 +476,9 @@ extern "C" {
|
|||||||
char* lang, int interim, char* bugname, void **ppUserData)
|
char* lang, int interim, char* bugname, void **ppUserData)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
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);
|
|
||||||
private_t* tech_pvt;
|
|
||||||
if (bug) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "reuse existing kep alive deepgram connection\n");
|
|
||||||
tech_pvt = (private_t*) switch_core_media_bug_get_user_data(bug);
|
|
||||||
} else {
|
|
||||||
tech_pvt = (private_t *) switch_core_session_alloc(session, sizeof(private_t));
|
|
||||||
tech_pvt->pAudioPipe = NULL;
|
|
||||||
tech_pvt->is_keep_alive = 0;
|
|
||||||
tech_pvt->mutex = NULL;
|
|
||||||
tech_pvt->resampler = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// allocate per-session data structure
|
||||||
|
private_t* tech_pvt = (private_t *) switch_core_session_alloc(session, sizeof(private_t));
|
||||||
if (!tech_pvt) {
|
if (!tech_pvt) {
|
||||||
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;
|
||||||
@@ -537,13 +491,16 @@ extern "C" {
|
|||||||
|
|
||||||
*ppUserData = tech_pvt;
|
*ppUserData = tech_pvt;
|
||||||
|
|
||||||
|
deepgram::AudioPipe *pAudioPipe = static_cast<deepgram::AudioPipe *>(tech_pvt->pAudioPipe);
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connecting now\n");
|
||||||
|
pAudioPipe->connect();
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "connection in progress\n");
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch_status_t dg_transcribe_session_stop(switch_core_session_t *session,int channelIsClosing, char* bugname) {
|
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_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, bugname);
|
||||||
const bool use_single_connection = switch_true(std::getenv("DEEPGRAM_SPEECH_USE_SINGLE_CONNECTION"));
|
|
||||||
if (!bug) {
|
if (!bug) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "dg_transcribe_session_stop: no bug - websocket conection already closed\n");
|
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;
|
return SWITCH_STATUS_FALSE;
|
||||||
@@ -554,12 +511,6 @@ extern "C" {
|
|||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) dg_transcribe_session_stop\n", id);
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) dg_transcribe_session_stop\n", id);
|
||||||
|
|
||||||
if (!tech_pvt) return SWITCH_STATUS_FALSE;
|
if (!tech_pvt) return SWITCH_STATUS_FALSE;
|
||||||
if (use_single_connection && !channelIsClosing) {
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "dg_transcribe_session_stop: call is running, use_single_connection is true, keep alive is activated\n", id);
|
|
||||||
tech_pvt->is_keep_alive = 1;
|
|
||||||
tech_pvt->frame_count = 0;
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// close connection and get final responses
|
// close connection and get final responses
|
||||||
switch_mutex_lock(tech_pvt->mutex);
|
switch_mutex_lock(tech_pvt->mutex);
|
||||||
@@ -567,7 +518,7 @@ extern "C" {
|
|||||||
if (!channelIsClosing) switch_core_media_bug_remove(session, &bug);
|
if (!channelIsClosing) switch_core_media_bug_remove(session, &bug);
|
||||||
|
|
||||||
deepgram::AudioPipe *pAudioPipe = static_cast<deepgram::AudioPipe *>(tech_pvt->pAudioPipe);
|
deepgram::AudioPipe *pAudioPipe = static_cast<deepgram::AudioPipe *>(tech_pvt->pAudioPipe);
|
||||||
if (pAudioPipe) reaper(tech_pvt, false);
|
if (pAudioPipe) reaper(tech_pvt);
|
||||||
destroy_tech_pvt(tech_pvt);
|
destroy_tech_pvt(tech_pvt);
|
||||||
switch_mutex_unlock(tech_pvt->mutex);
|
switch_mutex_unlock(tech_pvt->mutex);
|
||||||
switch_mutex_destroy(tech_pvt->mutex);
|
switch_mutex_destroy(tech_pvt->mutex);
|
||||||
@@ -581,31 +532,9 @@ extern "C" {
|
|||||||
size_t inuse = 0;
|
size_t inuse = 0;
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
char *p = (char *) "{\"msg\": \"buffer overrun\"}";
|
char *p = (char *) "{\"msg\": \"buffer overrun\"}";
|
||||||
char *keep_alive = (char *) "{\"type\": \"KeepAlive\"}";
|
|
||||||
|
|
||||||
if (!tech_pvt) return SWITCH_TRUE;
|
if (!tech_pvt) return SWITCH_TRUE;
|
||||||
|
|
||||||
|
|
||||||
// Keep sending keep alive if there is no transcribe activity
|
|
||||||
if (tech_pvt->is_keep_alive && tech_pvt->pAudioPipe) {
|
|
||||||
deepgram::AudioPipe *pAudioPipe = static_cast<deepgram::AudioPipe *>(tech_pvt->pAudioPipe);
|
|
||||||
if (++tech_pvt->frame_count * 20 /*ms*/ / 1000 >= DEEPGRAM_KEEP_ALIVE_INTERVAL_SECOND) {
|
|
||||||
tech_pvt->frame_count = 0;
|
|
||||||
pAudioPipe->bufferForSending(keep_alive);
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "dg_transcribe_frame: sending %s to deepgram\n", keep_alive);
|
|
||||||
}
|
|
||||||
// remove media bug buffered data
|
|
||||||
while (true) {
|
|
||||||
unsigned char data[SWITCH_RECOMMENDED_BUFFER_SIZE] = {0};
|
|
||||||
switch_frame_t frame = { 0 };
|
|
||||||
frame.data = data;
|
|
||||||
frame.buflen = SWITCH_RECOMMENDED_BUFFER_SIZE;
|
|
||||||
switch_status_t rv = switch_core_media_bug_read(bug, &frame, SWITCH_TRUE);
|
|
||||||
if (rv != SWITCH_STATUS_SUCCESS) break;
|
|
||||||
}
|
|
||||||
return SWITCH_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switch_mutex_trylock(tech_pvt->mutex) == SWITCH_STATUS_SUCCESS) {
|
if (switch_mutex_trylock(tech_pvt->mutex) == SWITCH_STATUS_SUCCESS) {
|
||||||
if (!tech_pvt->pAudioPipe) {
|
if (!tech_pvt->pAudioPipe) {
|
||||||
switch_mutex_unlock(tech_pvt->mutex);
|
switch_mutex_unlock(tech_pvt->mutex);
|
||||||
@@ -616,6 +545,7 @@ extern "C" {
|
|||||||
switch_mutex_unlock(tech_pvt->mutex);
|
switch_mutex_unlock(tech_pvt->mutex);
|
||||||
return SWITCH_TRUE;
|
return SWITCH_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
pAudioPipe->lockAudioBuffer();
|
pAudioPipe->lockAudioBuffer();
|
||||||
size_t available = pAudioPipe->binarySpaceAvailable();
|
size_t available = pAudioPipe->binarySpaceAvailable();
|
||||||
if (NULL == tech_pvt->resampler) {
|
if (NULL == tech_pvt->resampler) {
|
||||||
@@ -657,6 +587,7 @@ extern "C" {
|
|||||||
if (frame.datalen) {
|
if (frame.datalen) {
|
||||||
spx_uint32_t out_len = available >> 1; // space for samples which are 2 bytes
|
spx_uint32_t out_len = available >> 1; // space for samples which are 2 bytes
|
||||||
spx_uint32_t in_len = frame.samples;
|
spx_uint32_t in_len = frame.samples;
|
||||||
|
|
||||||
speex_resampler_process_interleaved_int(tech_pvt->resampler,
|
speex_resampler_process_interleaved_int(tech_pvt->resampler,
|
||||||
(const spx_int16_t *) frame.data,
|
(const spx_int16_t *) frame.data,
|
||||||
(spx_uint32_t *) &in_len,
|
(spx_uint32_t *) &in_len,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
#include "mod_deepgram_transcribe.h"
|
#include "mod_deepgram_transcribe.h"
|
||||||
#include "dg_transcribe_glue.h"
|
#include "dg_transcribe_glue.h"
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/* Prototypes */
|
/* Prototypes */
|
||||||
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_deepgram_transcribe_shutdown);
|
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_deepgram_transcribe_shutdown);
|
||||||
@@ -73,10 +72,8 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
|||||||
switch_codec_implementation_t read_impl = { 0 };
|
switch_codec_implementation_t read_impl = { 0 };
|
||||||
void *pUserData;
|
void *pUserData;
|
||||||
uint32_t samples_per_second;
|
uint32_t samples_per_second;
|
||||||
int use_single_connection = switch_true(getenv("DEEPGRAM_SPEECH_USE_SINGLE_CONNECTION"));
|
|
||||||
bug = switch_channel_get_private(channel, bugname);
|
|
||||||
|
|
||||||
if (bug && !use_single_connection) {
|
if (switch_channel_get_private(channel, bugname)) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing bug from previous transcribe\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing bug from previous transcribe\n");
|
||||||
do_stop(session, bugname);
|
do_stop(session, bugname);
|
||||||
}
|
}
|
||||||
@@ -93,13 +90,11 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
|||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error initializing dg speech session.\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error initializing dg speech session.\n");
|
||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
if (!bug || !use_single_connection) {
|
if ((status = switch_core_media_bug_add(session, "dg_transcribe", NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
|
||||||
if ((status = switch_core_media_bug_add(session, "dg_transcribe", NULL, capture_callback, pUserData, 0, flags, &bug)) != SWITCH_STATUS_SUCCESS) {
|
return status;
|
||||||
return status;
|
|
||||||
}
|
|
||||||
switch_channel_set_private(channel, bugname, bug);
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "added media bug for dg transcribe\n");
|
|
||||||
}
|
}
|
||||||
|
switch_channel_set_private(channel, bugname, bug);
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "added media bug for dg transcribe\n");
|
||||||
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ struct private_data {
|
|||||||
unsigned int id;
|
unsigned int id;
|
||||||
int buffer_overrun_notified:1;
|
int buffer_overrun_notified:1;
|
||||||
int is_finished:1;
|
int is_finished:1;
|
||||||
int is_keep_alive;
|
|
||||||
int frame_count;
|
|
||||||
char configuration[MAX_PATH_LEN];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct private_data private_t;
|
typedef struct private_data private_t;
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
|||||||
auto d = conn->deepgram;
|
auto d = conn->deepgram;
|
||||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) d->circularBuffer;
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) d->circularBuffer;
|
||||||
|
|
||||||
if (conn->flushed || cBuffer == nullptr) {
|
if (conn->flushed) {
|
||||||
/* this will abort the transfer */
|
/* this will abort the transfer */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -641,7 +641,7 @@ extern "C" {
|
|||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullDirPath = std::string(baseDir) + "tts-cache-files";
|
fullDirPath = std::string(baseDir) + "jambonz-tts-cache-files";
|
||||||
|
|
||||||
// Create the directory with read, write, and execute permissions for everyone
|
// Create the directory with read, write, and execute permissions for everyone
|
||||||
mode_t oldMask = umask(0);
|
mode_t oldMask = umask(0);
|
||||||
@@ -764,8 +764,6 @@ extern "C" {
|
|||||||
conn->file = d->file;
|
conn->file = d->file;
|
||||||
conn->body = json;
|
conn->body = json;
|
||||||
conn->flushed = false;
|
conn->flushed = false;
|
||||||
conn->has_last_byte = false;
|
|
||||||
conn->last_byte = 0;
|
|
||||||
|
|
||||||
|
|
||||||
d->circularBuffer = (void *) new CircularBuffer_t(BUFFER_GROW_SIZE);
|
d->circularBuffer = (void *) new CircularBuffer_t(BUFFER_GROW_SIZE);
|
||||||
@@ -805,10 +803,7 @@ extern "C" {
|
|||||||
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
||||||
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
||||||
|
|
||||||
// libcurl adding random byte to the response body that creates white noise to audio file
|
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||||
// https://github.com/curl/curl/issues/10525
|
|
||||||
const bool disable_http_2 = switch_true(std::getenv("DISABLE_HTTP2_FOR_TTS_STREAMING"));
|
|
||||||
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, disable_http_2 ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_2_0);
|
|
||||||
|
|
||||||
rc = curl_multi_add_handle(global.multi, conn->easy);
|
rc = curl_multi_add_handle(global.multi, conn->easy);
|
||||||
mcode_test("new_conn: curl_multi_add_handle", rc);
|
mcode_test("new_conn: curl_multi_add_handle", rc);
|
||||||
|
|||||||
@@ -84,8 +84,6 @@ static switch_status_t d_speech_feed_tts(switch_speech_handle_t *sh, char *text,
|
|||||||
deepgram_t *d = createOrRetrievePrivateData(sh);
|
deepgram_t *d = createOrRetrievePrivateData(sh);
|
||||||
d->draining = 0;
|
d->draining = 0;
|
||||||
d->reads = 0;
|
d->reads = 0;
|
||||||
d->response_code = 0;
|
|
||||||
d->err_msg = NULL;
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "d_speech_feed_tts\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "d_speech_feed_tts\n");
|
||||||
|
|
||||||
|
|||||||
+1
-4
@@ -136,10 +136,7 @@ void AudioProducerHttp::start(std::function<void(bool, const std::string&)> call
|
|||||||
/* call this function to close a socket */
|
/* call this function to close a socket */
|
||||||
curl_easy_setopt(_easy, CURLOPT_CLOSESOCKETFUNCTION, &AudioProducerHttp::close_socket);
|
curl_easy_setopt(_easy, CURLOPT_CLOSESOCKETFUNCTION, &AudioProducerHttp::close_socket);
|
||||||
|
|
||||||
// libcurl adding random byte to the response body that creates white noise to audio file
|
curl_easy_setopt(_easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||||
// https://github.com/curl/curl/issues/10525
|
|
||||||
const bool disable_http_2 = switch_true(std::getenv("DISABLE_HTTP2_FOR_TTS_STREAMING"));
|
|
||||||
curl_easy_setopt(_easy, CURLOPT_HTTP_VERSION, disable_http_2 ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_2_0);
|
|
||||||
|
|
||||||
/* keep the speed down so we don't have to buffer large amounts*/
|
/* keep the speed down so we don't have to buffer large amounts*/
|
||||||
curl_easy_setopt(_easy, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)31415);
|
curl_easy_setopt(_easy, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)31415);
|
||||||
|
|||||||
@@ -445,12 +445,15 @@ static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
|||||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) el->circularBuffer;
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) el->circularBuffer;
|
||||||
std::vector<uint16_t> pcm_data;
|
std::vector<uint16_t> pcm_data;
|
||||||
|
|
||||||
if (conn->flushed || cBuffer == nullptr) {
|
if (conn->flushed) {
|
||||||
/* this will abort the transfer */
|
/* this will abort the transfer */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
switch_mutex_lock(el->mutex);
|
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) {
|
if (el->response_code > 0 && el->response_code != 200) {
|
||||||
std::string body((char *) ptr, bytes_received);
|
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());
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "write_cb: received body %s\n", body.c_str());
|
||||||
@@ -685,7 +688,7 @@ extern "C" {
|
|||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullDirPath = std::string(baseDir) + "tts-cache-files";
|
fullDirPath = std::string(baseDir) + "jambonz-tts-cache-files";
|
||||||
|
|
||||||
// Create the directory with read, write, and execute permissions for everyone
|
// Create the directory with read, write, and execute permissions for everyone
|
||||||
mode_t oldMask = umask(0);
|
mode_t oldMask = umask(0);
|
||||||
@@ -838,6 +841,7 @@ extern "C" {
|
|||||||
CURL* easy = createEasyHandle();
|
CURL* easy = createEasyHandle();
|
||||||
|
|
||||||
el->conn = (void *) conn ;
|
el->conn = (void *) conn ;
|
||||||
|
el->sample_rate = 0;
|
||||||
conn->elevenlabs = el;
|
conn->elevenlabs = el;
|
||||||
conn->easy = easy;
|
conn->easy = easy;
|
||||||
conn->global = &global;
|
conn->global = &global;
|
||||||
@@ -848,12 +852,20 @@ extern "C" {
|
|||||||
|
|
||||||
el->circularBuffer = (void *) new CircularBuffer_t(8192);
|
el->circularBuffer = (void *) new CircularBuffer_t(8192);
|
||||||
|
|
||||||
if (el->rate != 8000 /*Hz*/) {
|
if (el->session_id) {
|
||||||
int err;
|
int err;
|
||||||
el->resampler = speex_resampler_init(1, 8000, el->rate, SWITCH_RESAMPLE_QUALITY, &err);
|
switch_codec_implementation_t read_impl;
|
||||||
if (0 != err) {
|
switch_core_session_t *psession = switch_core_session_locate(el->session_id);
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error initializing resampler: %s.\n", speex_resampler_strerror(err));
|
switch_core_session_get_read_impl(psession, &read_impl);
|
||||||
return SWITCH_STATUS_FALSE;
|
uint32_t samples_per_second = !strcasecmp(read_impl.iananame, "g722") ? read_impl.actual_samples_per_second : read_impl.samples_per_second;
|
||||||
|
el->sample_rate = samples_per_second;
|
||||||
|
// elevenlabs output is PCMU 8000
|
||||||
|
if (samples_per_second != 8000 /*Hz*/) {
|
||||||
|
el->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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,10 +895,7 @@ extern "C" {
|
|||||||
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
||||||
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
||||||
|
|
||||||
// libcurl adding random byte to the response body that creates white noise to audio file
|
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||||
// https://github.com/curl/curl/issues/10525
|
|
||||||
const bool disable_http_2 = switch_true(std::getenv("DISABLE_HTTP2_FOR_TTS_STREAMING"));
|
|
||||||
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, disable_http_2 ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_2_0);
|
|
||||||
|
|
||||||
rc = curl_multi_add_handle(global.multi, conn->easy);
|
rc = curl_multi_add_handle(global.multi, conn->easy);
|
||||||
mcode_test("new_conn: curl_multi_add_handle", rc);
|
mcode_test("new_conn: curl_multi_add_handle", rc);
|
||||||
@@ -930,7 +939,9 @@ extern "C" {
|
|||||||
switch_mutex_unlock(el->mutex);
|
switch_mutex_unlock(el->mutex);
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
size_t size = std::min((*datalen/(2 * el->rate / 8000)), cBuffer->size());
|
size_t size = el->sample_rate ?
|
||||||
|
std::min((*datalen/(2 * el->sample_rate / 8000)), cBuffer->size()) :
|
||||||
|
std::min((*datalen/2), cBuffer->size());
|
||||||
pcm_data.insert(pcm_data.end(), cBuffer->begin(), cBuffer->begin() + size);
|
pcm_data.insert(pcm_data.end(), cBuffer->begin(), cBuffer->begin() + size);
|
||||||
cBuffer->erase(cBuffer->begin(), cBuffer->begin() + size);
|
cBuffer->erase(cBuffer->begin(), cBuffer->begin() + size);
|
||||||
switch_mutex_unlock(el->mutex);
|
switch_mutex_unlock(el->mutex);
|
||||||
|
|||||||
@@ -106,8 +106,6 @@ static switch_status_t ell_speech_feed_tts(switch_speech_handle_t *sh, char *tex
|
|||||||
elevenlabs_t *el = createOrRetrievePrivateData(sh);
|
elevenlabs_t *el = createOrRetrievePrivateData(sh);
|
||||||
el->draining = 0;
|
el->draining = 0;
|
||||||
el->reads = 0;
|
el->reads = 0;
|
||||||
el->response_code = 0;
|
|
||||||
el->err_msg = NULL;
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "ell_speech_feed_tts\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "ell_speech_feed_tts\n");
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ struct elevenlabs_data {
|
|||||||
char *cache_filename;
|
char *cache_filename;
|
||||||
|
|
||||||
int rate;
|
int rate;
|
||||||
|
uint32_t sample_rate;
|
||||||
|
|
||||||
void *conn;
|
void *conn;
|
||||||
FILE *file;
|
FILE *file;
|
||||||
|
|||||||
@@ -236,7 +236,6 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
|
|||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "grpc_read_thread: error %s (%d)\n", 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* json = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(json, "type", "error");
|
cJSON_AddStringToObject(json, "type", "error");
|
||||||
cJSON_AddStringToObject(json, "error_cause", "stream_response");
|
|
||||||
cJSON_AddStringToObject(json, "error", status.message().c_str());
|
cJSON_AddStringToObject(json, "error", status.message().c_str());
|
||||||
char* jsonString = cJSON_PrintUnformatted(json);
|
char* jsonString = cJSON_PrintUnformatted(json);
|
||||||
cb->responseHandler(session, jsonString, cb->bugname);
|
cb->responseHandler(session, jsonString, cb->bugname);
|
||||||
@@ -340,10 +339,9 @@ 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 if (status.error_code() != 0) {
|
else {
|
||||||
cJSON* json = cJSON_CreateObject();
|
cJSON* json = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(json, "type", "error");
|
cJSON_AddStringToObject(json, "type", "error");
|
||||||
cJSON_AddStringToObject(json, "error_cause", "stream_close");
|
|
||||||
cJSON_AddItemToObject(json, "error_code", cJSON_CreateNumber(status.error_code()));
|
cJSON_AddItemToObject(json, "error_code", cJSON_CreateNumber(status.error_code()));
|
||||||
cJSON_AddStringToObject(json, "error_message", status.error_message().c_str());
|
cJSON_AddStringToObject(json, "error_message", status.error_message().c_str());
|
||||||
char* jsonString = cJSON_PrintUnformatted(json);
|
char* jsonString = cJSON_PrintUnformatted(json);
|
||||||
|
|||||||
@@ -349,7 +349,6 @@ static void *SWITCH_THREAD_FUNC grpc_read_thread(switch_thread_t *thread, void *
|
|||||||
else if (status.error_code() != 0) {
|
else if (status.error_code() != 0) {
|
||||||
cJSON* json = cJSON_CreateObject();
|
cJSON* json = cJSON_CreateObject();
|
||||||
cJSON_AddStringToObject(json, "type", "error");
|
cJSON_AddStringToObject(json, "type", "error");
|
||||||
cJSON_AddStringToObject(json, "error_cause", "stream_close");
|
|
||||||
cJSON_AddItemToObject(json, "error_code", cJSON_CreateNumber(status.error_code()));
|
cJSON_AddItemToObject(json, "error_code", cJSON_CreateNumber(status.error_code()));
|
||||||
cJSON_AddStringToObject(json, "error_message", status.error_message().c_str());
|
cJSON_AddStringToObject(json, "error_message", status.error_message().c_str());
|
||||||
char* jsonString = cJSON_PrintUnformatted(json);
|
char* jsonString = cJSON_PrintUnformatted(json);
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
include $(top_srcdir)/build/modmake.rulesam
|
||||||
|
MODNAME=mod_google_tts
|
||||||
|
|
||||||
|
mod_LTLIBRARIES = mod_google_tts.la
|
||||||
|
mod_google_tts_la_SOURCES = mod_google_tts.c google_glue.cpp
|
||||||
|
mod_google_tts_la_CFLAGS = $(AM_CFLAGS)
|
||||||
|
mod_google_tts_la_CXXFLAGS = -I $(top_srcdir)/libs/googleapis/gens $(AM_CXXFLAGS) -std=c++17
|
||||||
|
mod_google_tts_la_LIBADD = $(switch_builddir)/libfreeswitch.la
|
||||||
|
mod_google_tts_la_LDFLAGS = -avoid-version -module -no-undefined -shared `pkg-config --libs grpc++ grpc`
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# mod_google_tts
|
||||||
|
|
||||||
|
A Freeswitch module that allows speak text to speech audio from Google stream.
|
||||||
@@ -0,0 +1,405 @@
|
|||||||
|
#include "mod_google_tts.h"
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#include <boost/circular_buffer.hpp>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "google/cloud/texttospeech/v1/cloud_tts.grpc.pb.h"
|
||||||
|
#include <grpc++/grpc++.h>
|
||||||
|
|
||||||
|
using google::cloud::texttospeech::v1::SynthesizeSpeechRequest;
|
||||||
|
using google::cloud::texttospeech::v1::SynthesizeSpeechResponse;
|
||||||
|
using google::cloud::texttospeech::v1::TextToSpeech;
|
||||||
|
using google::cloud::texttospeech::v1::Voice;
|
||||||
|
using google::cloud::texttospeech::v1::SsmlVoiceGender;
|
||||||
|
using google::cloud::texttospeech::v1::SsmlVoiceGender_Name;
|
||||||
|
using google::cloud::texttospeech::v1::SynthesisInput;
|
||||||
|
using google::cloud::texttospeech::v1::AudioEncoding;
|
||||||
|
|
||||||
|
#define BUFFER_SIZE 8129
|
||||||
|
|
||||||
|
typedef boost::circular_buffer<uint16_t> CircularBuffer_t;
|
||||||
|
|
||||||
|
static std::string fullDirPath;
|
||||||
|
|
||||||
|
static std::shared_ptr<grpc::Channel> create_grpc_channel(switch_channel_t *channel) {
|
||||||
|
const char* google_uri = "texttospeech.googleapis.com";
|
||||||
|
const char* var;
|
||||||
|
if (var = switch_channel_get_variable(channel, "GOOGLE_TTS_APPLICATION_CREDENTIALS")) {
|
||||||
|
auto channelCreds = grpc::SslCredentials(grpc::SslCredentialsOptions());
|
||||||
|
auto callCreds = grpc::ServiceAccountJWTAccessCredentials(var);
|
||||||
|
auto creds = grpc::CompositeChannelCredentials(channelCreds, callCreds);
|
||||||
|
return grpc::CreateChannel(google_uri, creds);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
auto creds = grpc::GoogleDefaultCredentials();
|
||||||
|
return grpc::CreateChannel(google_uri, creds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void start_synthesis(const char* text, google_t* g) {
|
||||||
|
try {
|
||||||
|
SynthesizeSpeechRequest request;
|
||||||
|
SynthesizeSpeechResponse response;
|
||||||
|
grpc::ClientContext context;
|
||||||
|
auto input = request.mutable_input();
|
||||||
|
auto voice = request.mutable_voice();
|
||||||
|
auto custom_voice = voice->mutable_custom_voice();
|
||||||
|
auto audio_config = request.mutable_audio_config();
|
||||||
|
/* lock and unlock session */
|
||||||
|
switch_core_session_t *psession = switch_core_session_locate(g->session_id);
|
||||||
|
switch_channel_t *swChannel = switch_core_session_get_channel(psession);
|
||||||
|
switch_core_session_rwunlock(psession);
|
||||||
|
auto channel = create_grpc_channel(swChannel);
|
||||||
|
auto stub = TextToSpeech::NewStub(channel);
|
||||||
|
|
||||||
|
if (strstr(text, "<speak") == text) {
|
||||||
|
input->set_ssml(text);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input->set_text(text);
|
||||||
|
}
|
||||||
|
if (g->gender) {
|
||||||
|
if (strcmp(g->gender, "MALE") == 0) {
|
||||||
|
voice->set_ssml_gender(google::cloud::texttospeech::v1::SsmlVoiceGender::MALE);
|
||||||
|
} else if (strcmp(g->gender, "FEMALE") == 0) {
|
||||||
|
voice->set_ssml_gender(google::cloud::texttospeech::v1::SsmlVoiceGender::FEMALE);
|
||||||
|
} else if (strcmp(g->gender, "NEUTRAL") == 0) {
|
||||||
|
voice->set_ssml_gender(google::cloud::texttospeech::v1::SsmlVoiceGender::NEUTRAL);
|
||||||
|
} else {
|
||||||
|
voice->set_ssml_gender(google::cloud::texttospeech::v1::SsmlVoiceGender::SSML_VOICE_GENDER_UNSPECIFIED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g->model) {
|
||||||
|
custom_voice->set_model(g->model);
|
||||||
|
if (strcmp(g->reported_usage, "OFFLINE") == 0) {
|
||||||
|
custom_voice->set_reported_usage(google::cloud::texttospeech::v1::CustomVoiceParams_ReportedUsage::CustomVoiceParams_ReportedUsage_OFFLINE);
|
||||||
|
} else if (strcmp(g->reported_usage, "REALTIME") == 0) {
|
||||||
|
custom_voice->set_reported_usage(google::cloud::texttospeech::v1::CustomVoiceParams_ReportedUsage::CustomVoiceParams_ReportedUsage_REALTIME);
|
||||||
|
} else {
|
||||||
|
custom_voice->set_reported_usage(google::cloud::texttospeech::v1::CustomVoiceParams_ReportedUsage::CustomVoiceParams_ReportedUsage_REPORTED_USAGE_UNSPECIFIED);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
voice->set_name(g->voice_name);
|
||||||
|
}
|
||||||
|
voice->set_language_code(g->language);
|
||||||
|
audio_config->set_audio_encoding(AudioEncoding::LINEAR16);
|
||||||
|
audio_config->set_sample_rate_hertz(8000);
|
||||||
|
grpc::Status status = stub->SynthesizeSpeech(&context, request, &response);
|
||||||
|
if (!status.ok()) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR,
|
||||||
|
"start_synthesis: error synthesizing speech: %s: details: %s\n",
|
||||||
|
status.error_message().c_str(), status.error_details().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g->response_code = 200;
|
||||||
|
auto audioData = response.audio_content();
|
||||||
|
if (g->flushed) return;
|
||||||
|
bool fireEvent = false;
|
||||||
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) g->circularBuffer;
|
||||||
|
if (g->file) {
|
||||||
|
// google return linear16 wav data that contains 44 bytes wav header, let remove them
|
||||||
|
fwrite(audioData.data() + 44, 1, audioData.size() - 44, g->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this sort of reinterpretation can be dangerous as a general rule, but in this case we know that the data
|
||||||
|
* is 16-bit PCM, so it's safe to do this and its much faster than copying the data byte by byte
|
||||||
|
*/
|
||||||
|
// google return linear16 wav data that contains 44 bytes wav header, let remove them
|
||||||
|
const uint16_t* begin = reinterpret_cast<const uint16_t*>(audioData.data() + 44);
|
||||||
|
const uint16_t* end = reinterpret_cast<const uint16_t*>(audioData.data() + audioData.size());
|
||||||
|
|
||||||
|
/* lock as briefly as possible */
|
||||||
|
switch_mutex_lock(g->mutex);
|
||||||
|
if (cBuffer->capacity() - cBuffer->size() < audioData.size()) {
|
||||||
|
cBuffer->set_capacity(cBuffer->size() + std::max( audioData.size(), (size_t)BUFFER_SIZE));
|
||||||
|
}
|
||||||
|
cBuffer->insert(cBuffer->end(), begin, end);
|
||||||
|
switch_mutex_unlock(g->mutex);
|
||||||
|
|
||||||
|
if (0 == g->reads++) {
|
||||||
|
fireEvent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fireEvent && g->session_id) {
|
||||||
|
auto endTime = std::chrono::high_resolution_clock::now();
|
||||||
|
auto startTime = *static_cast<std::chrono::time_point<std::chrono::high_resolution_clock>*>(g->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(g->session_id);
|
||||||
|
if (session) {
|
||||||
|
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||||
|
switch_core_session_rwunlock(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, "Playback-File-Type", "tts_stream");
|
||||||
|
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_time_to_first_byte_ms", time_to_first_byte_ms.c_str());
|
||||||
|
if (g->cache_filename) {
|
||||||
|
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", g->cache_filename);
|
||||||
|
}
|
||||||
|
switch_event_fire(&event);
|
||||||
|
} else {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mod_google_tts start_synthesis: failed to create event\n");
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mod_google_tts start_synthesis: channel not found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
g->response_code = 500;
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "mod_google_tts: Exception in start_synthesis %s\n", e.what());
|
||||||
|
}
|
||||||
|
g->draining = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
switch_status_t google_speech_load() {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "google_speech_loading..\n");
|
||||||
|
|
||||||
|
/* create temp folder for cache files */
|
||||||
|
const char* baseDir = std::getenv("JAMBONZ_TMP_CACHE_FOLDER");
|
||||||
|
if (!baseDir) {
|
||||||
|
baseDir = "/tmp/";
|
||||||
|
}
|
||||||
|
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, "google_speech_loaded..\n");
|
||||||
|
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_status_t google_speech_open(google_t* google) {
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_status_t google_speech_feed_tts(google_t* g, 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 (g->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);
|
||||||
|
g->cache_filename = strdup(outfile);
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "writing audio cache file to %s\n", g->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 {
|
||||||
|
g->file = fdopen(fd, "wb");
|
||||||
|
if (!g->file) {
|
||||||
|
close(fd);
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error opening cache file %s: %s\n", outfile, strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g->language) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "google_speech_feed_tts: no language provided\n");
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g->voice_name && !g->model) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "google_speech_feed_tts: no voice_name or model provided\n");
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g->rate != 8000 /*Hz*/) {
|
||||||
|
int err;
|
||||||
|
g->resampler = speex_resampler_init(1, 8000, g->rate, 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());
|
||||||
|
g->startTime = ptr;
|
||||||
|
|
||||||
|
g->circularBuffer = (void *) new CircularBuffer_t(BUFFER_SIZE);
|
||||||
|
|
||||||
|
std::thread(start_synthesis, text, g).detach();
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "google_speech_feed_tts sent synthesize request\n");
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_status_t google_speech_read_tts(google_t* g, void *data, size_t *datalen, switch_speech_flag_t *flags) {
|
||||||
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) g->circularBuffer;
|
||||||
|
std::vector<uint16_t> pcm_data;
|
||||||
|
|
||||||
|
if (g->response_code > 0 && g->response_code != 200) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "google_speech_read_tts, returning failure\n") ;
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
|
if (g->flushed) {
|
||||||
|
return SWITCH_STATUS_BREAK;
|
||||||
|
}
|
||||||
|
switch_mutex_lock(g->mutex);
|
||||||
|
size_t bufSize = cBuffer->size();
|
||||||
|
if (cBuffer->empty()) {
|
||||||
|
switch_mutex_unlock(g->mutex);
|
||||||
|
if (g->draining) {
|
||||||
|
return SWITCH_STATUS_BREAK;
|
||||||
|
}
|
||||||
|
/* no audio available yet so send silence */
|
||||||
|
memset(data, 255, *datalen);
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
// google returned 8000hz 16 bit data, we have to take enough data based on call sample rate.
|
||||||
|
size_t size = std::min((*datalen/(2 * g->rate / 8000)), bufSize);
|
||||||
|
pcm_data.insert(pcm_data.end(), cBuffer->begin(), cBuffer->begin() + size);
|
||||||
|
cBuffer->erase(cBuffer->begin(), cBuffer->begin() + size);
|
||||||
|
switch_mutex_unlock(g->mutex);
|
||||||
|
|
||||||
|
size_t data_size = pcm_data.size();
|
||||||
|
|
||||||
|
if (g->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(g->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 google_speech_flush_tts(google_t* g) {
|
||||||
|
bool download_complete = g->response_code == 200;
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "google_speech_flush_tts, download complete? %s\n", download_complete ? "yes" : "no") ;
|
||||||
|
|
||||||
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) g->circularBuffer;
|
||||||
|
delete cBuffer;
|
||||||
|
g->circularBuffer = nullptr ;
|
||||||
|
delete static_cast<std::chrono::time_point<std::chrono::high_resolution_clock>*>(g->startTime);
|
||||||
|
g->startTime = nullptr;
|
||||||
|
|
||||||
|
g->flushed = 1;
|
||||||
|
if (!download_complete) {
|
||||||
|
if (g->file) {
|
||||||
|
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "closing audio cache file %s because download was interrupted\n", g->cache_filename);
|
||||||
|
if (fclose(g->file) != 0) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "error closing audio cache file\n");
|
||||||
|
}
|
||||||
|
g->file = nullptr ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g->cache_filename) {
|
||||||
|
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "removing audio cache file %s because download was interrupted\n", g->cache_filename);
|
||||||
|
if (unlink(g->cache_filename) != 0) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "cleanupConn: error removing audio cache file %s: %d:%s\n",
|
||||||
|
g->cache_filename, errno, strerror(errno));
|
||||||
|
}
|
||||||
|
free(g->cache_filename);
|
||||||
|
g->cache_filename = nullptr ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (g->session_id) {
|
||||||
|
switch_core_session_t* session = switch_core_session_locate(g->session_id);
|
||||||
|
if (session) {
|
||||||
|
switch_channel_t *channel = switch_core_session_get_channel(session);
|
||||||
|
|
||||||
|
/* unlock as quickly as possible */
|
||||||
|
switch_core_session_rwunlock(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_google_response_code", std::to_string(g->response_code).c_str());
|
||||||
|
if (g->cache_filename && download_complete) {
|
||||||
|
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_cache_filename", g->cache_filename);
|
||||||
|
}
|
||||||
|
if (!download_complete && g->err_msg) {
|
||||||
|
switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "variable_tts_error", g->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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_status_t google_speech_close(google_t* g) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "google_speech_close\n") ;
|
||||||
|
if (g->resampler) {
|
||||||
|
speex_resampler_destroy(g->resampler);
|
||||||
|
}
|
||||||
|
|
||||||
|
g->resampler = NULL;
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_status_t google_speech_unload() {
|
||||||
|
return SWITCH_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef __GOOGLE_TTS_GLUE_H__
|
||||||
|
#define __GOOGLE_TTS_GLUE_H__
|
||||||
|
|
||||||
|
switch_status_t google_speech_load();
|
||||||
|
switch_status_t google_speech_open(google_t* google);
|
||||||
|
switch_status_t google_speech_feed_tts(google_t* google, char* text, switch_speech_flag_t *flags);
|
||||||
|
switch_status_t google_speech_read_tts(google_t* google, void *data, size_t *datalen, switch_speech_flag_t *flags);
|
||||||
|
switch_status_t google_speech_flush_tts(google_t* google);
|
||||||
|
switch_status_t google_speech_close(google_t* google);
|
||||||
|
switch_status_t google_speech_unload();
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
#include "mod_google_tts.h"
|
||||||
|
#include "google_glue.h"
|
||||||
|
|
||||||
|
SWITCH_MODULE_LOAD_FUNCTION(mod_google_tts_load);
|
||||||
|
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_google_tts_shutdown);
|
||||||
|
SWITCH_MODULE_DEFINITION(mod_google_tts, mod_google_tts_load, mod_google_tts_shutdown, NULL);
|
||||||
|
|
||||||
|
static void clearGoogle(google_t* g, int freeAll) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "clearGoogle\n");
|
||||||
|
|
||||||
|
if (g->cache_filename) free(g->cache_filename);
|
||||||
|
if (g->model) free(g->model);
|
||||||
|
if (g->reported_usage) free(g->reported_usage);
|
||||||
|
if (g->gender) free(g->gender);
|
||||||
|
if (g->language) free(g->language);
|
||||||
|
if (g->err_msg) free(g->err_msg);
|
||||||
|
|
||||||
|
g->cache_filename = NULL;
|
||||||
|
g->model = NULL;
|
||||||
|
g->reported_usage = NULL;
|
||||||
|
g->gender = NULL;
|
||||||
|
g->language = NULL;
|
||||||
|
g->err_msg = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
if (freeAll) {
|
||||||
|
if (g->voice_name) free(g->voice_name);
|
||||||
|
if (g->session_id) free(g->session_id);
|
||||||
|
g->voice_name = NULL;
|
||||||
|
g->session_id = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static google_t * createOrRetrievePrivateData(switch_speech_handle_t *sh) {
|
||||||
|
google_t *g = (google_t *) sh->private_info;
|
||||||
|
if (!g) {
|
||||||
|
g = switch_core_alloc(sh->memory_pool, sizeof(*g));
|
||||||
|
sh->private_info = g;
|
||||||
|
memset(g, 0, sizeof(*g));
|
||||||
|
switch_mutex_init(&g->mutex, SWITCH_MUTEX_NESTED, sh->memory_pool);
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "allocated google_t\n");
|
||||||
|
}
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_status_t g_speech_open(switch_speech_handle_t *sh, const char *voice_name, int rate, int channels, switch_speech_flag_t *flags)
|
||||||
|
{
|
||||||
|
google_t *g = createOrRetrievePrivateData(sh);
|
||||||
|
g->voice_name = strdup(voice_name);
|
||||||
|
g->rate = rate;
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "g_speech_open voice: %s, rate %d, channels %d\n", voice_name, rate, channels);
|
||||||
|
return google_speech_open(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
static switch_status_t g_speech_close(switch_speech_handle_t *sh, switch_speech_flag_t *flags)
|
||||||
|
{
|
||||||
|
switch_status_t rc;
|
||||||
|
google_t *g = createOrRetrievePrivateData(sh);
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "g_speech_close\n");
|
||||||
|
|
||||||
|
switch_mutex_destroy(g->mutex);
|
||||||
|
|
||||||
|
rc = google_speech_close(g);
|
||||||
|
clearGoogle(g, 1);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freeswitch will call this function to feed us text to speak
|
||||||
|
*/
|
||||||
|
static switch_status_t g_speech_feed_tts(switch_speech_handle_t *sh, char *text, switch_speech_flag_t *flags)
|
||||||
|
{
|
||||||
|
google_t *g = createOrRetrievePrivateData(sh);
|
||||||
|
g->draining = 0;
|
||||||
|
g->reads = 0;
|
||||||
|
g->flushed = 0;
|
||||||
|
|
||||||
|
return google_speech_feed_tts(g, 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 g_speech_read_tts(switch_speech_handle_t *sh, void *data, size_t *datalen, switch_speech_flag_t *flags)
|
||||||
|
{
|
||||||
|
google_t *g = createOrRetrievePrivateData(sh);
|
||||||
|
return google_speech_read_tts(g, data, datalen, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called at the end, not sure exactly what we need to do here..
|
||||||
|
*/
|
||||||
|
static void g_speech_flush_tts(switch_speech_handle_t *sh)
|
||||||
|
{
|
||||||
|
google_t *g = createOrRetrievePrivateData(sh);
|
||||||
|
google_speech_flush_tts(g);
|
||||||
|
|
||||||
|
clearGoogle(g, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void g_text_param_tts(switch_speech_handle_t *sh, char *param, const char *val)
|
||||||
|
{
|
||||||
|
google_t *g = createOrRetrievePrivateData(sh);
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "g_text_param_tts: %s=%s\n", param, val);
|
||||||
|
if (0 == strcmp(param, "voice")) {
|
||||||
|
if (g->voice_name) free(g->voice_name);
|
||||||
|
g->voice_name = strdup(val);
|
||||||
|
} else if (0 == strcmp(param, "model")) {
|
||||||
|
if (g->model) free(g->model);
|
||||||
|
g->model = strdup(val);
|
||||||
|
} else if (0 == strcmp(param, "reported_usage")) {
|
||||||
|
if (g->reported_usage) free(g->reported_usage);
|
||||||
|
g->reported_usage = strdup(val);
|
||||||
|
} else if (0 == strcmp(param, "language")) {
|
||||||
|
if (g->language) free(g->language);
|
||||||
|
g->language = strdup(val);
|
||||||
|
} else if (0 == strcmp(param, "session-uuid")) {
|
||||||
|
if (g->session_id) free(g->session_id);
|
||||||
|
g->session_id = strdup(val);
|
||||||
|
} else if (0 == strcmp(param, "write_cache_file") && switch_true(val)) {
|
||||||
|
g->cache_audio = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void g_numeric_param_tts(switch_speech_handle_t *sh, char *param, int val)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
static void g_float_param_tts(switch_speech_handle_t *sh, char *param, double val)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SWITCH_MODULE_LOAD_FUNCTION(mod_google_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 = "google";
|
||||||
|
speech_interface->speech_open = g_speech_open;
|
||||||
|
speech_interface->speech_close = g_speech_close;
|
||||||
|
speech_interface->speech_feed_tts = g_speech_feed_tts;
|
||||||
|
speech_interface->speech_read_tts = g_speech_read_tts;
|
||||||
|
speech_interface->speech_flush_tts = g_speech_flush_tts;
|
||||||
|
speech_interface->speech_text_param_tts = g_text_param_tts;
|
||||||
|
speech_interface->speech_numeric_param_tts = g_numeric_param_tts;
|
||||||
|
speech_interface->speech_float_param_tts = g_float_param_tts;
|
||||||
|
return google_speech_load();
|
||||||
|
}
|
||||||
|
|
||||||
|
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_google_tts_shutdown)
|
||||||
|
{
|
||||||
|
return google_speech_unload();
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef __MOD_GOOGLE_TTS_H__
|
||||||
|
#define __MOD_GOOGLE_TTS_H__
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
#include <speex/speex_resampler.h>
|
||||||
|
|
||||||
|
typedef struct google_data {
|
||||||
|
char *voice_name;
|
||||||
|
char *model;
|
||||||
|
char *reported_usage;
|
||||||
|
char *language;
|
||||||
|
char *gender;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
void *startTime;
|
||||||
|
|
||||||
|
FILE *file;
|
||||||
|
SpeexResamplerState *resampler;
|
||||||
|
void *circularBuffer;
|
||||||
|
switch_mutex_t *mutex;
|
||||||
|
} google_t;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -404,14 +404,12 @@ extern "C" {
|
|||||||
jambonz::AudioPipe *pAudioPipe = static_cast<jambonz::AudioPipe *>(tech_pvt->pAudioPipe);
|
jambonz::AudioPipe *pAudioPipe = static_cast<jambonz::AudioPipe *>(tech_pvt->pAudioPipe);
|
||||||
if (pAudioPipe) reaper(tech_pvt);
|
if (pAudioPipe) reaper(tech_pvt);
|
||||||
destroy_tech_pvt(tech_pvt);
|
destroy_tech_pvt(tech_pvt);
|
||||||
switch_mutex_unlock(tech_pvt->mutex);
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) jb_transcribe_session_stop, bug removed\n", id);
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "(%u) jb_transcribe_session_stop, bug removed\n", id);
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "jb_transcribe_session_stop: race condition, previous close completed\n");
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "jb_transcribe_session_stop: race condition, previous close completed\n");
|
||||||
}
|
}
|
||||||
switch_mutex_unlock(tech_pvt->mutex);
|
|
||||||
}
|
}
|
||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ static switch_status_t start_capture(switch_core_session_t *session, switch_medi
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
switch_channel_set_private(channel, bugname, bug);
|
switch_channel_set_private(channel, bugname, bug);
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "added media bug for jb transcribe: %s\n", bugname);
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "added media bug for jb transcribe\n");
|
||||||
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ SWITCH_STANDARD_API(jb_transcribe_function)
|
|||||||
if ((lsession = switch_core_session_locate(argv[0]))) {
|
if ((lsession = switch_core_session_locate(argv[0]))) {
|
||||||
if (!strcasecmp(argv[1], "stop")) {
|
if (!strcasecmp(argv[1], "stop")) {
|
||||||
char *bugname = argc > 2 ? argv[2] : MY_BUG_NAME;
|
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);
|
status = do_stop(lsession, bugname);
|
||||||
} else if (!strcasecmp(argv[1], "start")) {
|
} else if (!strcasecmp(argv[1], "start")) {
|
||||||
char* lang = argv[2];
|
char* lang = argv[2];
|
||||||
|
|||||||
@@ -99,8 +99,6 @@ static switch_status_t p_speech_feed_tts(switch_speech_handle_t *sh, char *text,
|
|||||||
playht_t *p = createOrRetrievePrivateData(sh);
|
playht_t *p = createOrRetrievePrivateData(sh);
|
||||||
p->draining = 0;
|
p->draining = 0;
|
||||||
p->reads = 0;
|
p->reads = 0;
|
||||||
p->response_code = 0;
|
|
||||||
p->err_msg = NULL;
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "p_speech_feed_tts\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "p_speech_feed_tts\n");
|
||||||
|
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
|||||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) p->circularBuffer;
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) p->circularBuffer;
|
||||||
std::vector<uint16_t> pcm_data;
|
std::vector<uint16_t> pcm_data;
|
||||||
|
|
||||||
if (conn->flushed || cBuffer == nullptr) {
|
if (conn->flushed) {
|
||||||
/* this will abort the transfer */
|
/* this will abort the transfer */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -648,7 +648,7 @@ extern "C" {
|
|||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullDirPath = std::string(baseDir) + "tts-cache-files";
|
fullDirPath = std::string(baseDir) + "jambonz-tts-cache-files";
|
||||||
|
|
||||||
// Create the directory with read, write, and execute permissions for everyone
|
// Create the directory with read, write, and execute permissions for everyone
|
||||||
mode_t oldMask = umask(0);
|
mode_t oldMask = umask(0);
|
||||||
@@ -838,9 +838,17 @@ extern "C" {
|
|||||||
|
|
||||||
p->circularBuffer = (void *) new CircularBuffer_t(8192);
|
p->circularBuffer = (void *) new CircularBuffer_t(8192);
|
||||||
|
|
||||||
if (mpg123_param(mh, MPG123_FORCE_RATE, p->rate /*Hz*/, 0) != MPG123_OK) {
|
if (p->session_id) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error mpg123_param!\n");
|
int err;
|
||||||
return SWITCH_STATUS_FALSE;
|
switch_codec_implementation_t read_impl;
|
||||||
|
switch_core_session_t *psession = switch_core_session_locate(p->session_id);
|
||||||
|
switch_core_session_get_read_impl(psession, &read_impl);
|
||||||
|
switch_core_session_rwunlock(psession);
|
||||||
|
uint32_t samples_per_second = !strcasecmp(read_impl.iananame, "g722") ? read_impl.actual_samples_per_second : read_impl.samples_per_second;
|
||||||
|
if (mpg123_param(mh, MPG123_FORCE_RATE, samples_per_second /*Hz*/, 0) != MPG123_OK) {
|
||||||
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error mpg123_param!\n");
|
||||||
|
return SWITCH_STATUS_FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostringstream api_key_stream;
|
std::ostringstream api_key_stream;
|
||||||
@@ -873,10 +881,7 @@ extern "C" {
|
|||||||
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
||||||
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
||||||
|
|
||||||
// libcurl adding random byte to the response body that creates white noise to audio file
|
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||||
// https://github.com/curl/curl/issues/10525
|
|
||||||
const bool disable_http_2 = switch_true(std::getenv("DISABLE_HTTP2_FOR_TTS_STREAMING"));
|
|
||||||
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, disable_http_2 ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_2_0);
|
|
||||||
|
|
||||||
rc = curl_multi_add_handle(global.multi, conn->easy);
|
rc = curl_multi_add_handle(global.multi, conn->easy);
|
||||||
mcode_test("new_conn: curl_multi_add_handle", rc);
|
mcode_test("new_conn: curl_multi_add_handle", rc);
|
||||||
|
|||||||
@@ -82,8 +82,6 @@ static switch_status_t d_speech_feed_tts(switch_speech_handle_t *sh, char *text,
|
|||||||
rimelabs_t *d = createOrRetrievePrivateData(sh);
|
rimelabs_t *d = createOrRetrievePrivateData(sh);
|
||||||
d->draining = 0;
|
d->draining = 0;
|
||||||
d->reads = 0;
|
d->reads = 0;
|
||||||
d->response_code = 0;
|
|
||||||
d->err_msg = NULL;
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "d_speech_feed_tts\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "d_speech_feed_tts\n");
|
||||||
|
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
|||||||
auto d = conn->rimelabs;
|
auto d = conn->rimelabs;
|
||||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) d->circularBuffer;
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) d->circularBuffer;
|
||||||
|
|
||||||
if (conn->flushed || cBuffer == nullptr) {
|
if (conn->flushed) {
|
||||||
/* this will abort the transfer */
|
/* this will abort the transfer */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -394,8 +394,6 @@ static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
|||||||
std::unique_ptr<uint8_t[]> combinedData;
|
std::unique_ptr<uint8_t[]> combinedData;
|
||||||
|
|
||||||
if (conn->has_last_byte) {
|
if (conn->has_last_byte) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "rimelabs write_cb: processing last byte from previous read\n");
|
|
||||||
|
|
||||||
conn->has_last_byte = false; // We'll handle the last_byte now, so toggle the flag off
|
conn->has_last_byte = false; // We'll handle the last_byte now, so toggle the flag off
|
||||||
|
|
||||||
// Allocate memory for the new data array
|
// Allocate memory for the new data array
|
||||||
@@ -417,8 +415,6 @@ static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
|||||||
|
|
||||||
// If we now have an odd total, save the last byte for next time
|
// If we now have an odd total, save the last byte for next time
|
||||||
if ((total_bytes_to_process % sizeof(int16_t)) != 0) {
|
if ((total_bytes_to_process % sizeof(int16_t)) != 0) {
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "rimelabs write_cb: got odd number of bytes %d\n", total_bytes_to_process);
|
|
||||||
|
|
||||||
conn->last_byte = data[total_bytes_to_process - 1];
|
conn->last_byte = data[total_bytes_to_process - 1];
|
||||||
conn->has_last_byte = true;
|
conn->has_last_byte = true;
|
||||||
total_bytes_to_process--;
|
total_bytes_to_process--;
|
||||||
@@ -630,7 +626,7 @@ extern "C" {
|
|||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullDirPath = std::string(baseDir) + "tts-cache-files";
|
fullDirPath = std::string(baseDir) + "jambonz-tts-cache-files";
|
||||||
|
|
||||||
// Create the directory with read, write, and execute permissions for everyone
|
// Create the directory with read, write, and execute permissions for everyone
|
||||||
mode_t oldMask = umask(0);
|
mode_t oldMask = umask(0);
|
||||||
@@ -762,8 +758,6 @@ extern "C" {
|
|||||||
conn->file = d->file;
|
conn->file = d->file;
|
||||||
conn->body = json;
|
conn->body = json;
|
||||||
conn->flushed = false;
|
conn->flushed = false;
|
||||||
conn->has_last_byte = false;
|
|
||||||
conn->last_byte = 0;
|
|
||||||
|
|
||||||
|
|
||||||
d->circularBuffer = (void *) new CircularBuffer_t(BUFFER_GROW_SIZE);
|
d->circularBuffer = (void *) new CircularBuffer_t(BUFFER_GROW_SIZE);
|
||||||
@@ -804,10 +798,7 @@ extern "C" {
|
|||||||
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
||||||
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
||||||
|
|
||||||
// libcurl adding random byte to the response body that creates white noise to audio file
|
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||||
// https://github.com/curl/curl/issues/10525
|
|
||||||
const bool disable_http_2 = switch_true(std::getenv("DISABLE_HTTP2_FOR_TTS_STREAMING"));
|
|
||||||
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, disable_http_2 ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_2_0);
|
|
||||||
|
|
||||||
rc = curl_multi_add_handle(global.multi, conn->easy);
|
rc = curl_multi_add_handle(global.multi, conn->easy);
|
||||||
mcode_test("new_conn: curl_multi_add_handle", rc);
|
mcode_test("new_conn: curl_multi_add_handle", rc);
|
||||||
|
|||||||
@@ -90,8 +90,6 @@ static switch_status_t w_speech_feed_tts(switch_speech_handle_t *sh, char *text,
|
|||||||
whisper_t *w = createOrRetrievePrivateData(sh);
|
whisper_t *w = createOrRetrievePrivateData(sh);
|
||||||
w->draining = 0;
|
w->draining = 0;
|
||||||
w->reads = 0;
|
w->reads = 0;
|
||||||
w->response_code = 0;
|
|
||||||
w->err_msg = NULL;
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "w_speech_feed_tts\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "w_speech_feed_tts\n");
|
||||||
|
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ static size_t write_cb(void *ptr, size_t size, size_t nmemb, ConnInfo_t *conn) {
|
|||||||
CircularBuffer_t *cBuffer = (CircularBuffer_t *) w->circularBuffer;
|
CircularBuffer_t *cBuffer = (CircularBuffer_t *) w->circularBuffer;
|
||||||
std::vector<uint16_t> pcm_data;
|
std::vector<uint16_t> pcm_data;
|
||||||
|
|
||||||
if (conn->flushed || cBuffer == nullptr) {
|
if (conn->flushed) {
|
||||||
/* this will abort the transfer */
|
/* this will abort the transfer */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -671,7 +671,7 @@ extern "C" {
|
|||||||
return SWITCH_STATUS_FALSE;
|
return SWITCH_STATUS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullDirPath = std::string(baseDir) + "tts-cache-files";
|
fullDirPath = std::string(baseDir) + "jambonz-tts-cache-files";
|
||||||
|
|
||||||
// Create the directory with read, write, and execute permissions for everyone
|
// Create the directory with read, write, and execute permissions for everyone
|
||||||
mode_t oldMask = umask(0);
|
mode_t oldMask = umask(0);
|
||||||
@@ -876,10 +876,7 @@ extern "C" {
|
|||||||
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, conn->body);
|
||||||
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
//curl_easy_setopt(easy, CURLOPT_POSTFIELDSIZE, body.length());
|
||||||
|
|
||||||
// libcurl adding random byte to the response body that creates white noise to audio file
|
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
||||||
// https://github.com/curl/curl/issues/10525
|
|
||||||
const bool disable_http_2 = switch_true(std::getenv("DISABLE_HTTP2_FOR_TTS_STREAMING"));
|
|
||||||
curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, disable_http_2 ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_2_0);
|
|
||||||
|
|
||||||
rc = curl_multi_add_handle(global.multi, conn->easy);
|
rc = curl_multi_add_handle(global.multi, conn->easy);
|
||||||
mcode_test("new_conn: curl_multi_add_handle", rc);
|
mcode_test("new_conn: curl_multi_add_handle", rc);
|
||||||
|
|||||||
Reference in New Issue
Block a user