mirror of
https://github.com/jambonz/freeswitch-modules.git
synced 2025-12-19 08:47:44 +00:00
* support tts elevenlabs to mod_dub Signed-off-by: Quan HL <quan.luuhoang8@gmail.com> * wip Signed-off-by: Quan HL <quan.luuhoang8@gmail.com> * wip * wip * fix aws race condition when 2 start transcribes are sent at the same instant * wip * wip * wip * allow queue play on track * wip Signed-off-by: Quan HL <quan.luuhoang8@gmail.com> * revert change for aws transcribe Signed-off-by: Quan HL <quan.luuhoang8@gmail.com> * fix type * wip * wip * rename parameters * rename paramters * wip * wip * wip * wip * wip * bug: there exists scenarios where callback is not defined * wip * wip * revert unintended changes to mod_google_transcribe * fix bugs w/ streaming tts simplified arg parsing to use freeswitch conventions --------- Signed-off-by: Quan HL <quan.luuhoang8@gmail.com> Co-authored-by: Quan HL <quan.luuhoang8@gmail.com>
196 lines
6.7 KiB
C++
196 lines
6.7 KiB
C++
#include "switch.h"
|
|
#include "ap_file.h"
|
|
#include "mpg_decode.h"
|
|
|
|
#define INIT_BUFFER_SIZE (80000)
|
|
#define BUFFER_GROW_SIZE (80000)
|
|
#define BUFFER_THROTTLE_LOW (40000)
|
|
#define BUFFER_THROTTLE_HIGH (160000)
|
|
|
|
bool AudioProducerFile::initialized = false;
|
|
boost::asio::io_service AudioProducerFile::io_service;
|
|
std::thread AudioProducerFile::worker_thread;
|
|
|
|
void AudioProducerFile::threadFunc() {
|
|
io_service.reset() ;
|
|
boost::asio::io_service::work work(io_service);
|
|
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "file_loader threadFunc - starting\n");
|
|
|
|
for(;;) {
|
|
|
|
try {
|
|
io_service.run() ;
|
|
break ;
|
|
}
|
|
catch( std::exception& e) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "file_loader threadFunc - Error: %s\n", e.what());
|
|
}
|
|
}
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "file_loader threadFunc - ending\n");
|
|
}
|
|
|
|
void AudioProducerFile::_init() {
|
|
if (!initialized) {
|
|
initialized = true;
|
|
if (mpg123_init() != MPG123_OK) {
|
|
throw std::runtime_error("AudioProducerFile::AudioProducerFile: failed to initiate MPG123");
|
|
return ;
|
|
}
|
|
|
|
/* start worker thread */
|
|
std::thread t(threadFunc) ;
|
|
worker_thread.swap( t ) ;
|
|
}
|
|
}
|
|
|
|
void AudioProducerFile::_deinit() {
|
|
if (initialized) {
|
|
initialized = false;
|
|
io_service.stop();
|
|
if (worker_thread.joinable()) {
|
|
worker_thread.join();
|
|
}
|
|
mpg123_exit();
|
|
}
|
|
}
|
|
AudioProducerFile::AudioProducerFile(
|
|
std::mutex& mutex,
|
|
CircularBuffer_t& circularBuffer,
|
|
int sampleRate
|
|
) : AudioProducer(mutex, circularBuffer, sampleRate), _timer(io_service), _mh(nullptr) {
|
|
|
|
AudioProducerFile::_init();
|
|
}
|
|
|
|
AudioProducerFile::~AudioProducerFile() {
|
|
reset();
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "AudioProducerFile::~AudioProducerFile %p\n", (void *)this);
|
|
}
|
|
|
|
void AudioProducerFile::queueFileAudio(const std::string& path, int gain, bool loop) {
|
|
_path = path;
|
|
_gain = gain;
|
|
_loop = loop;
|
|
|
|
/* we only handle mp3 or r8 files atm */
|
|
size_t pos = path.find_last_of('.');
|
|
if (pos == std::string::npos) {
|
|
throw std::runtime_error("file " + path + " has no extension");
|
|
}
|
|
auto filetype = path.substr(pos + 1);
|
|
if (0 == filetype.compare("mp3")) _type = FILE_TYPE_MP3;
|
|
else if (0 == filetype.compare("r8")) _type = FILE_TYPE_R8;
|
|
else throw std::runtime_error("file " + path + " has unsupported extension " + filetype);
|
|
}
|
|
|
|
void AudioProducerFile::start(std::function<void(bool, const std::string&)> callback) {
|
|
int mhError = 0;
|
|
|
|
_callback = callback;
|
|
|
|
/* allocate handle for mpeg decoding */
|
|
_mh = mpg123_new("auto", &mhError);
|
|
if (!_mh) {
|
|
const char *mhErr = mpg123_plain_strerror(mhError);
|
|
throw std::runtime_error("Error allocating mpg123 handle! " + std::string(mhErr));
|
|
}
|
|
|
|
if (mpg123_open_feed(_mh) != MPG123_OK) throw std::runtime_error("Error mpg123_open_feed!");
|
|
if (mpg123_format_all(_mh) != MPG123_OK) throw std::runtime_error("Error mpg123_format_all!");
|
|
if (mpg123_param(_mh, MPG123_FORCE_RATE, _sampleRate, 0) != MPG123_OK) throw std::runtime_error("Error forcing resample to 8k!");
|
|
if (mpg123_param(_mh, MPG123_FLAGS, MPG123_MONO_MIX, 0) != MPG123_OK) throw std::runtime_error("Error forcing single channel!");
|
|
|
|
_fp = fopen(_path.c_str(), "rb");
|
|
if (!_fp) throw std::runtime_error("Error opening file " + _path);
|
|
|
|
_status = Status_t::STATUS_AWAITING_RESTART;
|
|
|
|
/* do the initial read in the worker thread so we don't block here */
|
|
_timer.expires_from_now(boost::posix_time::millisec(1));
|
|
_timer.async_wait(boost::bind(&AudioProducerFile::read_cb, this, boost::placeholders::_1));
|
|
}
|
|
|
|
void AudioProducerFile::read_cb(const boost::system::error_code& error) {
|
|
if (_status == Status_t::STATUS_STOPPED) {
|
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "read_cb: session gone\n");
|
|
return;
|
|
}
|
|
if (_status == Status_t::STATUS_AWAITING_RESTART) {
|
|
_status = Status_t::STATUS_IN_PROGRESS;
|
|
}
|
|
if (!error) {
|
|
size_t size = _buffer.size();
|
|
if (size < BUFFER_THROTTLE_LOW) {
|
|
std::vector<int16_t> pcm_data;
|
|
int8_t buf[INIT_BUFFER_SIZE];
|
|
|
|
size_t bytesRead = ::fread(buf, sizeof(int8_t), INIT_BUFFER_SIZE, _fp);
|
|
if (bytesRead <= 0) {
|
|
if (::feof(_fp)) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "read_cb: %p eof\n", (void *) this);
|
|
else if (::ferror(_fp)) switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "read_cb: %p error reading file\n", (void *) this);
|
|
else switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "read_cb: %p unknown error reading file\n", (void *) this);
|
|
_status = Status_t::STATUS_COMPLETE;
|
|
return;
|
|
}
|
|
|
|
if (_type == FileType_t::FILE_TYPE_MP3) pcm_data = convert_mp3_to_linear(_mh, _gain, buf, bytesRead);
|
|
else pcm_data = std::vector<int16_t>(reinterpret_cast<int16_t*>(buf), reinterpret_cast<int16_t*>(buf) + bytesRead / 2);
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
// Resize the buffer if necessary
|
|
size_t capacity = _buffer.capacity();
|
|
if (capacity - size < pcm_data.size()) {
|
|
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "write_cb %p growing buffer, size now %ld\n", (void *) this, size);
|
|
_buffer.set_capacity(size + std::max(pcm_data.size(), (size_t)BUFFER_GROW_SIZE));
|
|
}
|
|
|
|
/* Push the data into the buffer */
|
|
_buffer.insert(_buffer.end(), pcm_data.data(), pcm_data.data() + pcm_data.size());
|
|
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "read_cb: %p wrote data, buffer size is now %ld\n", (void *) this, _buffer.size());
|
|
}
|
|
|
|
if (bytesRead < INIT_BUFFER_SIZE) {
|
|
_status = Status_t::STATUS_COMPLETE;
|
|
//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "read_cb: %u reached end of file, status is %s\n", (void *) this, status2String(_status));
|
|
}
|
|
}
|
|
|
|
if (_status == Status_t::STATUS_COMPLETE) {
|
|
cleanup(Status_t::STATUS_COMPLETE);
|
|
}
|
|
else {
|
|
// read more in 2 seconds
|
|
_timer.expires_from_now(boost::posix_time::millisec(2000));
|
|
_timer.async_wait(boost::bind(&AudioProducerFile::read_cb, this, boost::placeholders::_1));
|
|
}
|
|
} else {
|
|
cleanup(Status_t::STATUS_FAILED, error.message());
|
|
}
|
|
}
|
|
|
|
void AudioProducerFile::stop() {
|
|
cleanup(Status_t::STATUS_STOPPED, "");
|
|
}
|
|
|
|
void AudioProducerFile::reset() {
|
|
if (_fp) {
|
|
fclose(_fp);
|
|
_fp = nullptr;
|
|
}
|
|
if (_mh) {
|
|
mpg123_close(_mh);
|
|
mpg123_delete(_mh);
|
|
_mh = nullptr;
|
|
}
|
|
_timer.cancel();
|
|
_status = Status_t::STATUS_NONE;
|
|
}
|
|
|
|
void AudioProducerFile::cleanup(Status_t status, std::string errMsg) {
|
|
reset();
|
|
_status = status;
|
|
notifyDone(status != Status_t::STATUS_COMPLETE, errMsg);
|
|
} |