chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(FunASRWebscoket)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14 CACHE STRING "The C++ version to be used.")
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
option(ENABLE_WEBSOCKET "Whether to build websocket server" ON)
|
||||
option(ENABLE_PORTAUDIO "Whether to build portaudio" ON)
|
||||
option(ENABLE_GLOG "Whether to build glog" ON)
|
||||
option(ENABLE_FST "Whether to build openfst" ON) # ITN need openfst compiled
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
|
||||
option(GPU "Whether to build with GPU" OFF)
|
||||
|
||||
if(WIN32)
|
||||
file(REMOVE ${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/glog/src/config.h
|
||||
${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/glog/src/glog/export.h
|
||||
${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/glog/src/glog/logging.h
|
||||
${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/glog/src/glog/raw_logging.h
|
||||
${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/glog/src/glog/stl_logging.h
|
||||
${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/glog/src/glog/vlog_is_on.h)
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -fPIC")
|
||||
endif()
|
||||
|
||||
if(GPU)
|
||||
add_definitions(-DUSE_GPU)
|
||||
set(TORCH_DIR "/usr/local/lib/python3.8/dist-packages/torch")
|
||||
set(TORCH_BLADE_DIR "/usr/local/lib/python3.8/dist-packages/torch_blade")
|
||||
include_directories(${TORCH_DIR}/include)
|
||||
include_directories(${TORCH_DIR}/include/torch/csrc/api/include)
|
||||
link_directories(${TORCH_DIR}/lib)
|
||||
link_directories(${TORCH_BLADE_DIR})
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -D_GLIBCXX_USE_CXX11_ABI=0")
|
||||
endif()
|
||||
|
||||
if(ENABLE_WEBSOCKET)
|
||||
# cmake_policy(SET CMP0135 NEW)
|
||||
include(FetchContent)
|
||||
|
||||
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/websocket/websocketpp )
|
||||
FetchContent_Declare(websocketpp
|
||||
GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git
|
||||
GIT_TAG 0.8.2
|
||||
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/websocket
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(websocketpp)
|
||||
endif()
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/websocket)
|
||||
|
||||
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/asio/asio )
|
||||
FetchContent_Declare(asio
|
||||
URL https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-24-0.tar.gz
|
||||
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/asio
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(asio)
|
||||
endif()
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/asio/asio/include)
|
||||
|
||||
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/json/ChangeLog.md )
|
||||
FetchContent_Declare(json
|
||||
URL https://github.com/nlohmann/json/archive/refs/tags/v3.11.2.tar.gz
|
||||
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/json
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(json)
|
||||
endif()
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/json/include)
|
||||
|
||||
endif()
|
||||
|
||||
if(ENABLE_PORTAUDIO)
|
||||
include(FetchContent)
|
||||
|
||||
set(portaudio_URL "http://files.portaudio.com/archives/pa_stable_v190700_20210406.tgz")
|
||||
set(portaudio_URL2 "https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/pa_stable_v190700_20210406.tgz")
|
||||
set(portaudio_HASH "SHA256=47efbf42c77c19a05d22e627d42873e991ec0c1357219c0d74ce6a2948cb2def")
|
||||
|
||||
FetchContent_Declare(portaudio
|
||||
URL
|
||||
${portaudio_URL}
|
||||
${portaudio_URL2}
|
||||
URL_HASH ${portaudio_HASH}
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(portaudio)
|
||||
if(NOT portaudio_POPULATED)
|
||||
message(STATUS "Downloading portaudio from ${portaudio_URL}")
|
||||
FetchContent_Populate(portaudio)
|
||||
endif()
|
||||
message(STATUS "portaudio is downloaded to ${portaudio_SOURCE_DIR}")
|
||||
message(STATUS "portaudio's binary dir is ${portaudio_BINARY_DIR}")
|
||||
|
||||
add_subdirectory(${portaudio_SOURCE_DIR} ${portaudio_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
if(NOT WIN32)
|
||||
target_compile_options(portaudio PRIVATE "-Wno-deprecated-declarations")
|
||||
else()
|
||||
install(TARGETS portaudio DESTINATION ..)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
# Include generated *.pb.h files
|
||||
link_directories(${ONNXRUNTIME_DIR}/lib)
|
||||
link_directories(${FFMPEG_DIR}/lib)
|
||||
|
||||
if(ENABLE_GLOG)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/glog/src)
|
||||
set(BUILD_TESTING OFF)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/glog glog)
|
||||
include_directories(${glog_BINARY_DIR})
|
||||
|
||||
endif()
|
||||
|
||||
if(ENABLE_FST)
|
||||
# fst depend on glog and gflags
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/gflags)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/gflags gflags)
|
||||
include_directories(${gflags_BINARY_DIR}/include)
|
||||
|
||||
# the following openfst if cloned from https://github.com/kkm000/openfst.git
|
||||
# with some patch to fix the make errors.
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/openfst openfst)
|
||||
include_directories(${openfst_SOURCE_DIR}/src/include)
|
||||
if(WIN32)
|
||||
include_directories(${openfst_SOURCE_DIR}/src/lib)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/include/)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/src)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/yaml-cpp/include/)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/kaldi-native-fbank)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/jieba/include)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/jieba/include/limonp/include)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/kaldi)
|
||||
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/yaml-cpp yaml-cpp)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/kaldi-native-fbank/kaldi-native-fbank/csrc csrc)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../onnxruntime/src src)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../onnxruntime/third_party/kaldi kaldi)
|
||||
|
||||
# install openssl first apt-get install libssl-dev
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
message("CXX_FLAGS "${CMAKE_CXX_FLAGS})
|
||||
# 获取项目中所有包含文件夹的路径
|
||||
get_property(includes DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
|
||||
# 遍历并输出每个包含文件夹的路径
|
||||
foreach(include ${includes})
|
||||
message("Include directory: ${include}")
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(bin)
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
if(WIN32)
|
||||
include_directories(${ONNXRUNTIME_DIR}/include)
|
||||
include_directories(${FFMPEG_DIR}/include)
|
||||
include_directories(${OPENSSL_ROOT_DIR}//include)
|
||||
link_directories(${OPENSSL_ROOT_DIR}/lib)
|
||||
add_definitions(-D_WEBSOCKETPP_CPP11_RANDOM_DEVICE_)
|
||||
add_definitions(-D_WEBSOCKETPP_CPP11_TYPE_TRAITS_)
|
||||
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/bigobj>")
|
||||
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||
SET(RELATION_SOURCE "../../onnxruntime/src/resample.cpp" "../../onnxruntime/src/util.cpp" "../../onnxruntime/src/alignedmem.cpp" "../../onnxruntime/src/encode_converter.cpp")
|
||||
endif()
|
||||
|
||||
add_executable(funasr-wss-server "funasr-wss-server.cpp" "websocket-server.cpp" ${RELATION_SOURCE})
|
||||
add_executable(funasr-wss-server-2pass "funasr-wss-server-2pass.cpp" "websocket-server-2pass.cpp" ${RELATION_SOURCE})
|
||||
add_executable(funasr-wss-client "funasr-wss-client.cpp" ${RELATION_SOURCE})
|
||||
add_executable(funasr-wss-client-2pass "funasr-wss-client-2pass.cpp" "microphone.cpp" ${RELATION_SOURCE})
|
||||
|
||||
target_link_options(funasr-wss-server PRIVATE "-Wl,--no-as-needed")
|
||||
target_link_options(funasr-wss-server-2pass PRIVATE "-Wl,--no-as-needed")
|
||||
|
||||
target_link_libraries(funasr-wss-client PUBLIC funasr ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_SSL_LIBRARY})
|
||||
target_link_libraries(funasr-wss-client-2pass PUBLIC funasr ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_SSL_LIBRARY} portaudio)
|
||||
target_link_libraries(funasr-wss-server PUBLIC funasr ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_SSL_LIBRARY})
|
||||
target_link_libraries(funasr-wss-server-2pass PUBLIC funasr ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_SSL_LIBRARY})
|
||||
@@ -0,0 +1,658 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaomingwork */
|
||||
|
||||
// client for websocket, support multiple threads
|
||||
// ./funasr-wss-client --server-ip <string>
|
||||
// --port <string>
|
||||
// --wav-path <string>
|
||||
// [--thread-num <int>]
|
||||
// [--is-ssl <int>] [--]
|
||||
// [--version] [-h]
|
||||
// example:
|
||||
// ./funasr-wss-client --server-ip 127.0.0.1 --port 10095 --wav-path test.wav
|
||||
// --thread-num 1 --is-ssl 1
|
||||
|
||||
#define ASIO_STANDALONE 1
|
||||
#include <glog/logging.h>
|
||||
#include "portaudio.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <websocketpp/client.hpp>
|
||||
#include <websocketpp/common/thread.hpp>
|
||||
#include <websocketpp/config/asio_client.hpp>
|
||||
#include "util.h"
|
||||
#include "audio.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "tclap/CmdLine.h"
|
||||
#include "microphone.h"
|
||||
|
||||
/**
|
||||
* Define a semi-cross platform helper method that waits/sleeps for a bit.
|
||||
*/
|
||||
void WaitABit() {
|
||||
#ifdef WIN32
|
||||
Sleep(500);
|
||||
#else
|
||||
usleep(500);
|
||||
#endif
|
||||
}
|
||||
std::atomic<int> wav_index(0);
|
||||
|
||||
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
|
||||
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>
|
||||
context_ptr;
|
||||
using websocketpp::lib::bind;
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::placeholders::_2;
|
||||
context_ptr OnTlsInit(websocketpp::connection_hdl) {
|
||||
context_ptr ctx = websocketpp::lib::make_shared<asio::ssl::context>(
|
||||
asio::ssl::context::sslv23);
|
||||
|
||||
try {
|
||||
ctx->set_options(
|
||||
asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 |
|
||||
asio::ssl::context::no_sslv3 | asio::ssl::context::single_dh_use);
|
||||
|
||||
} catch (std::exception& e) {
|
||||
LOG(ERROR) << e.what();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// template for tls or not config
|
||||
template <typename T>
|
||||
class WebsocketClient {
|
||||
public:
|
||||
// typedef websocketpp::client<T> client;
|
||||
// typedef websocketpp::client<websocketpp::config::asio_tls_client>
|
||||
// wss_client;
|
||||
typedef websocketpp::lib::lock_guard<websocketpp::lib::mutex> scoped_lock;
|
||||
|
||||
WebsocketClient(int is_ssl) : m_open(false), m_done(false) {
|
||||
// set up access channels to only log interesting things
|
||||
m_client.clear_access_channels(websocketpp::log::alevel::all);
|
||||
m_client.set_access_channels(websocketpp::log::alevel::connect);
|
||||
m_client.set_access_channels(websocketpp::log::alevel::disconnect);
|
||||
m_client.set_access_channels(websocketpp::log::alevel::app);
|
||||
|
||||
// Initialize the Asio transport policy
|
||||
m_client.init_asio();
|
||||
|
||||
// Bind the handlers we are using
|
||||
using websocketpp::lib::bind;
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
m_client.set_open_handler(bind(&WebsocketClient::on_open, this, _1));
|
||||
m_client.set_close_handler(bind(&WebsocketClient::on_close, this, _1));
|
||||
|
||||
m_client.set_message_handler(
|
||||
[this](websocketpp::connection_hdl hdl, message_ptr msg) {
|
||||
on_message(hdl, msg);
|
||||
});
|
||||
|
||||
m_client.set_fail_handler(bind(&WebsocketClient::on_fail, this, _1));
|
||||
m_client.clear_access_channels(websocketpp::log::alevel::all);
|
||||
}
|
||||
|
||||
void on_message(websocketpp::connection_hdl hdl, message_ptr msg) {
|
||||
const std::string& payload = msg->get_payload();
|
||||
switch (msg->get_opcode()) {
|
||||
case websocketpp::frame::opcode::text:
|
||||
nlohmann::json jsonresult = nlohmann::json::parse(payload);
|
||||
LOG(INFO) << "Thread: " << this_thread::get_id()
|
||||
<< ",on_message = " << payload;
|
||||
|
||||
if (jsonresult["is_final"] == true) {
|
||||
websocketpp::lib::error_code ec;
|
||||
|
||||
m_client.close(hdl, websocketpp::close::status::going_away, "", ec);
|
||||
|
||||
if (ec) {
|
||||
LOG(ERROR) << "Error closing connection " << ec.message();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This method will block until the connection is complete
|
||||
void run(const std::string& uri, const std::vector<string>& wav_list,
|
||||
const std::vector<string>& wav_ids, int audio_fs, std::string asr_mode,
|
||||
std::vector<int> chunk_size, const std::unordered_map<std::string, int>& hws_map,
|
||||
bool is_record=false, int use_itn=1, int svs_itn=1) {
|
||||
// Create a new connection to the given URI
|
||||
websocketpp::lib::error_code ec;
|
||||
typename websocketpp::client<T>::connection_ptr con =
|
||||
m_client.get_connection(uri, ec);
|
||||
if (ec) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Get Connection Error: " + ec.message());
|
||||
return;
|
||||
}
|
||||
// Grab a handle for this connection so we can talk to it in a thread
|
||||
// safe manor after the event loop starts.
|
||||
m_hdl = con->get_handle();
|
||||
|
||||
// Queue the connection. No DNS queries or network connections will be
|
||||
// made until the io_service event loop is run.
|
||||
m_client.connect(con);
|
||||
|
||||
// Create a thread to run the ASIO io_service event loop
|
||||
websocketpp::lib::thread asio_thread(&websocketpp::client<T>::run,
|
||||
&m_client);
|
||||
if(is_record){
|
||||
send_rec_data(asr_mode, chunk_size, hws_map, use_itn, svs_itn);
|
||||
}else{
|
||||
send_wav_data(wav_list[0], wav_ids[0], audio_fs, asr_mode, chunk_size, hws_map, use_itn, svs_itn);
|
||||
}
|
||||
|
||||
WaitABit();
|
||||
|
||||
asio_thread.join();
|
||||
}
|
||||
|
||||
// The open handler will signal that we are ready to start sending data
|
||||
void on_open(websocketpp::connection_hdl) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Connection opened, starting data!");
|
||||
|
||||
scoped_lock guard(m_lock);
|
||||
m_open = true;
|
||||
}
|
||||
|
||||
// The close handler will signal that we should stop sending data
|
||||
void on_close(websocketpp::connection_hdl) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Connection closed, stopping data!");
|
||||
|
||||
scoped_lock guard(m_lock);
|
||||
m_done = true;
|
||||
}
|
||||
|
||||
// The fail handler will signal that we should stop sending data
|
||||
void on_fail(websocketpp::connection_hdl) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Connection failed, stopping data!");
|
||||
|
||||
scoped_lock guard(m_lock);
|
||||
m_done = true;
|
||||
}
|
||||
// send wav to server
|
||||
void send_wav_data(string wav_path, string wav_id, int audio_fs, std::string asr_mode,
|
||||
std::vector<int> chunk_vector, const std::unordered_map<std::string, int>& hws_map,
|
||||
int use_itn, int svs_itn) {
|
||||
uint64_t count = 0;
|
||||
std::stringstream val;
|
||||
|
||||
funasr::Audio audio(1);
|
||||
int32_t sampling_rate = audio_fs;
|
||||
std::string wav_format = "pcm";
|
||||
if (funasr::IsTargetFile(wav_path.c_str(), "wav")) {
|
||||
if (!audio.LoadWav(wav_path.c_str(), &sampling_rate, false))
|
||||
return;
|
||||
} else if (funasr::IsTargetFile(wav_path.c_str(), "pcm")) {
|
||||
if (!audio.LoadPcmwav(wav_path.c_str(), &sampling_rate, false)) return;
|
||||
} else {
|
||||
wav_format = "others";
|
||||
if (!audio.LoadOthers2Char(wav_path.c_str())) return;
|
||||
}
|
||||
|
||||
float* buff;
|
||||
int len;
|
||||
int flag = 0;
|
||||
bool wait = false;
|
||||
while (1) {
|
||||
{
|
||||
scoped_lock guard(m_lock);
|
||||
// If the connection has been closed, stop generating data
|
||||
if (m_done) {
|
||||
break;
|
||||
}
|
||||
// If the connection hasn't been opened yet wait a bit and retry
|
||||
if (!m_open) {
|
||||
wait = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
// LOG(INFO) << "wait.." << m_open;
|
||||
WaitABit();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
websocketpp::lib::error_code ec;
|
||||
|
||||
nlohmann::json jsonbegin;
|
||||
nlohmann::json chunk_size = nlohmann::json::array();
|
||||
chunk_size.push_back(chunk_vector[0]);
|
||||
chunk_size.push_back(chunk_vector[1]);
|
||||
chunk_size.push_back(chunk_vector[2]);
|
||||
jsonbegin["mode"] = asr_mode;
|
||||
jsonbegin["chunk_size"] = chunk_size;
|
||||
jsonbegin["wav_name"] = wav_id;
|
||||
jsonbegin["wav_format"] = wav_format;
|
||||
jsonbegin["audio_fs"] = sampling_rate;
|
||||
jsonbegin["is_speaking"] = true;
|
||||
jsonbegin["itn"] = true;
|
||||
jsonbegin["svs_itn"] = true;
|
||||
if(use_itn == 0){
|
||||
jsonbegin["itn"] = false;
|
||||
}
|
||||
if(svs_itn == 0){
|
||||
jsonbegin["svs_itn"] = false;
|
||||
}
|
||||
if(!hws_map.empty()){
|
||||
LOG(INFO) << "hotwords: ";
|
||||
for (const auto& pair : hws_map) {
|
||||
LOG(INFO) << pair.first << " : " << pair.second;
|
||||
}
|
||||
nlohmann::json json_map(hws_map);
|
||||
std::string json_map_str = json_map.dump();
|
||||
jsonbegin["hotwords"] = json_map_str;
|
||||
}
|
||||
m_client.send(m_hdl, jsonbegin.dump(), websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
|
||||
// fetch wav data use asr engine api
|
||||
if (wav_format == "pcm") {
|
||||
while (audio.Fetch(buff, len, flag) > 0) {
|
||||
short* iArray = new short[len];
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
iArray[i] = (short)(buff[i] * 32768);
|
||||
}
|
||||
|
||||
// send data to server
|
||||
int offset = 0;
|
||||
int block_size = 102400;
|
||||
while (offset < len) {
|
||||
int send_block = 0;
|
||||
if (offset + block_size <= len) {
|
||||
send_block = block_size;
|
||||
} else {
|
||||
send_block = len - offset;
|
||||
}
|
||||
m_client.send(m_hdl, iArray + offset, send_block * sizeof(short),
|
||||
websocketpp::frame::opcode::binary, ec);
|
||||
offset += send_block;
|
||||
}
|
||||
|
||||
LOG(INFO) << "sended data len=" << len * sizeof(short);
|
||||
if (ec) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Send Error: " + ec.message());
|
||||
break;
|
||||
}
|
||||
delete[] iArray;
|
||||
}
|
||||
} else {
|
||||
int offset = 0;
|
||||
int block_size = 204800;
|
||||
len = audio.GetSpeechLen();
|
||||
char* others_buff = audio.GetSpeechChar();
|
||||
|
||||
while (offset < len) {
|
||||
int send_block = 0;
|
||||
if (offset + block_size <= len) {
|
||||
send_block = block_size;
|
||||
} else {
|
||||
send_block = len - offset;
|
||||
}
|
||||
m_client.send(m_hdl, others_buff + offset, send_block,
|
||||
websocketpp::frame::opcode::binary, ec);
|
||||
offset += send_block;
|
||||
}
|
||||
|
||||
LOG(INFO) << "sended data len=" << len;
|
||||
if (ec) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Send Error: " + ec.message());
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json jsonresult;
|
||||
jsonresult["is_speaking"] = false;
|
||||
m_client.send(m_hdl, jsonresult.dump(), websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
WaitABit();
|
||||
}
|
||||
|
||||
static int RecordCallback(const void* inputBuffer, void* outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void* userData)
|
||||
{
|
||||
std::vector<float>* buffer = static_cast<std::vector<float>*>(userData);
|
||||
const float* input = static_cast<const float*>(inputBuffer);
|
||||
|
||||
for (unsigned int i = 0; i < framesPerBuffer; i++)
|
||||
{
|
||||
buffer->push_back(input[i]);
|
||||
}
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
void send_rec_data(std::string asr_mode, std::vector<int> chunk_vector,
|
||||
const std::unordered_map<std::string, int>& hws_map, int use_itn, int svs_itn) {
|
||||
// first message
|
||||
bool wait = false;
|
||||
while (1) {
|
||||
{
|
||||
scoped_lock guard(m_lock);
|
||||
// If the connection has been closed, stop generating data
|
||||
if (m_done) {
|
||||
break;
|
||||
}
|
||||
// If the connection hasn't been opened yet wait a bit and retry
|
||||
if (!m_open) {
|
||||
wait = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
// LOG(INFO) << "wait.." << m_open;
|
||||
WaitABit();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
websocketpp::lib::error_code ec;
|
||||
|
||||
float sample_rate = 16000;
|
||||
nlohmann::json jsonbegin;
|
||||
nlohmann::json chunk_size = nlohmann::json::array();
|
||||
chunk_size.push_back(chunk_vector[0]);
|
||||
chunk_size.push_back(chunk_vector[1]);
|
||||
chunk_size.push_back(chunk_vector[2]);
|
||||
jsonbegin["mode"] = asr_mode;
|
||||
jsonbegin["chunk_size"] = chunk_size;
|
||||
jsonbegin["wav_name"] = "record";
|
||||
jsonbegin["wav_format"] = "pcm";
|
||||
jsonbegin["audio_fs"] = sample_rate;
|
||||
jsonbegin["is_speaking"] = true;
|
||||
jsonbegin["itn"] = true;
|
||||
jsonbegin["svs_itn"] = true;
|
||||
if(use_itn == 0){
|
||||
jsonbegin["itn"] = false;
|
||||
}
|
||||
if(svs_itn == 0){
|
||||
jsonbegin["svs_itn"] = false;
|
||||
}
|
||||
if(!hws_map.empty()){
|
||||
LOG(INFO) << "hotwords: ";
|
||||
for (const auto& pair : hws_map) {
|
||||
LOG(INFO) << pair.first << " : " << pair.second;
|
||||
}
|
||||
nlohmann::json json_map(hws_map);
|
||||
std::string json_map_str = json_map.dump();
|
||||
jsonbegin["hotwords"] = json_map_str;
|
||||
}
|
||||
m_client.send(m_hdl, jsonbegin.dump(), websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
// mic
|
||||
Microphone mic;
|
||||
PaDeviceIndex num_devices = Pa_GetDeviceCount();
|
||||
LOG(INFO) << "Num devices: " << num_devices;
|
||||
|
||||
PaStreamParameters param;
|
||||
|
||||
param.device = Pa_GetDefaultInputDevice();
|
||||
if (param.device == paNoDevice) {
|
||||
LOG(INFO) << "No default input device found";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
LOG(INFO) << "Use default device: " << param.device;
|
||||
|
||||
const PaDeviceInfo *info = Pa_GetDeviceInfo(param.device);
|
||||
LOG(INFO) << " Name: " << info->name;
|
||||
LOG(INFO) << " Max input channels: " << info->maxInputChannels;
|
||||
|
||||
param.channelCount = 1;
|
||||
param.sampleFormat = paFloat32;
|
||||
|
||||
param.suggestedLatency = info->defaultLowInputLatency;
|
||||
param.hostApiSpecificStreamInfo = nullptr;
|
||||
|
||||
PaStream *stream;
|
||||
std::vector<float> buffer;
|
||||
PaError err =
|
||||
Pa_OpenStream(&stream, ¶m, nullptr, /* &outputParameters, */
|
||||
sample_rate,
|
||||
0, // frames per buffer
|
||||
paClipOff, // we won't output out of range samples
|
||||
// so don't bother clipping them
|
||||
RecordCallback, &buffer);
|
||||
if (err != paNoError) {
|
||||
LOG(ERROR) << "portaudio error: " << Pa_GetErrorText(err);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
err = Pa_StartStream(stream);
|
||||
LOG(INFO) << "Started: ";
|
||||
|
||||
if (err != paNoError) {
|
||||
LOG(ERROR) << "portaudio error: " << Pa_GetErrorText(err);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
while(true){
|
||||
int len = buffer.size();
|
||||
short* iArray = new short[len];
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
iArray[i] = (short)(buffer[i] * 32768);
|
||||
}
|
||||
|
||||
m_client.send(m_hdl, iArray, len * sizeof(short),
|
||||
websocketpp::frame::opcode::binary, ec);
|
||||
buffer.clear();
|
||||
|
||||
if (ec) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Send Error: " + ec.message());
|
||||
}
|
||||
delete[] iArray;
|
||||
Pa_Sleep(20); // sleep for 20ms
|
||||
}
|
||||
|
||||
nlohmann::json jsonresult;
|
||||
jsonresult["is_speaking"] = false;
|
||||
m_client.send(m_hdl, jsonresult.dump(), websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
|
||||
err = Pa_CloseStream(stream);
|
||||
if (err != paNoError) {
|
||||
LOG(INFO) << "portaudio error: " << Pa_GetErrorText(err);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
websocketpp::client<T> m_client;
|
||||
|
||||
private:
|
||||
websocketpp::connection_hdl m_hdl;
|
||||
websocketpp::lib::mutex m_lock;
|
||||
bool m_open;
|
||||
bool m_done;
|
||||
int total_num = 0;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
SetConsoleOutputCP(65001);
|
||||
#endif
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
FLAGS_logtostderr = true;
|
||||
|
||||
TCLAP::CmdLine cmd("funasr-wss-client-2pass", ' ', "1.0");
|
||||
TCLAP::ValueArg<std::string> server_ip_("", "server-ip", "server-ip", true,
|
||||
"127.0.0.1", "string");
|
||||
TCLAP::ValueArg<std::string> port_("", "port", "port", true, "10095",
|
||||
"string");
|
||||
TCLAP::ValueArg<std::string> wav_path_(
|
||||
"", "wav-path",
|
||||
"the input could be: wav_path, e.g.: asr_example.wav; pcm_path, e.g.: "
|
||||
"asr_example.pcm; wav.scp, kaldi style wav list (wav_id \t wav_path)",
|
||||
false, "", "string");
|
||||
TCLAP::ValueArg<std::int32_t> audio_fs_("", "audio-fs", "the sample rate of audio", false, 16000, "int32_t");
|
||||
TCLAP::ValueArg<int> record_(
|
||||
"", "record",
|
||||
"record is 1 means use record", false, 0,
|
||||
"int");
|
||||
TCLAP::ValueArg<std::string> asr_mode_("", ASR_MODE, "offline, online, 2pass",
|
||||
false, "2pass", "string");
|
||||
TCLAP::ValueArg<std::string> chunk_size_("", "chunk-size",
|
||||
"chunk_size: 5-10-5 or 5-12-5",
|
||||
false, "5-10-5", "string");
|
||||
TCLAP::ValueArg<int> thread_num_("", "thread-num", "thread-num", false, 1,
|
||||
"int");
|
||||
TCLAP::ValueArg<int> is_ssl_(
|
||||
"", "is-ssl",
|
||||
"is-ssl is 1 means use wss connection, or use ws connection", false, 1,
|
||||
"int");
|
||||
TCLAP::ValueArg<int> use_itn_(
|
||||
"", "use-itn",
|
||||
"use-itn is 1 means use itn, 0 means not use itn", false, 1,
|
||||
"int");
|
||||
TCLAP::ValueArg<int> svs_itn_(
|
||||
"", "svs-itn",
|
||||
"svs-itn is 1 means use itn and punc, 0 means not use", false, 1, "int");
|
||||
TCLAP::ValueArg<std::string> hotword_("", HOTWORD,
|
||||
"the hotword file, one hotword perline, Format: Hotword Weight (could be: 阿里巴巴 20)", false, "", "string");
|
||||
|
||||
cmd.add(server_ip_);
|
||||
cmd.add(port_);
|
||||
cmd.add(wav_path_);
|
||||
cmd.add(audio_fs_);
|
||||
cmd.add(asr_mode_);
|
||||
cmd.add(record_);
|
||||
cmd.add(chunk_size_);
|
||||
cmd.add(thread_num_);
|
||||
cmd.add(is_ssl_);
|
||||
cmd.add(use_itn_);
|
||||
cmd.add(svs_itn_);
|
||||
cmd.add(hotword_);
|
||||
cmd.parse(argc, argv);
|
||||
|
||||
std::string server_ip = server_ip_.getValue();
|
||||
std::string port = port_.getValue();
|
||||
std::string wav_path = wav_path_.getValue();
|
||||
std::string asr_mode = asr_mode_.getValue();
|
||||
std::string chunk_size_str = chunk_size_.getValue();
|
||||
int use_itn = use_itn_.getValue();
|
||||
int svs_itn = svs_itn_.getValue();
|
||||
// get chunk_size
|
||||
std::vector<int> chunk_size;
|
||||
std::stringstream ss(chunk_size_str);
|
||||
std::string item;
|
||||
while (std::getline(ss, item, '-')) {
|
||||
try {
|
||||
chunk_size.push_back(stoi(item));
|
||||
} catch (const invalid_argument&) {
|
||||
LOG(ERROR) << "Invalid argument: " << item;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
int threads_num = thread_num_.getValue();
|
||||
int is_ssl = is_ssl_.getValue();
|
||||
int is_record = record_.getValue();
|
||||
|
||||
std::string uri = "";
|
||||
if (is_ssl == 1) {
|
||||
uri = "wss://" + server_ip + ":" + port;
|
||||
} else {
|
||||
uri = "ws://" + server_ip + ":" + port;
|
||||
}
|
||||
|
||||
// hotwords
|
||||
std::string hotword_path = hotword_.getValue();
|
||||
unordered_map<string, int> hws_map;
|
||||
if(!hotword_path.empty()){
|
||||
LOG(INFO) << "hotword path: " << hotword_path;
|
||||
funasr::ExtractHws(hotword_path, hws_map);
|
||||
}
|
||||
|
||||
int audio_fs = audio_fs_.getValue();
|
||||
if(is_record == 1){
|
||||
std::vector<string> tmp_wav_list;
|
||||
std::vector<string> tmp_wav_ids;
|
||||
|
||||
if (is_ssl == 1) {
|
||||
WebsocketClient<websocketpp::config::asio_tls_client> c(is_ssl);
|
||||
|
||||
c.m_client.set_tls_init_handler(bind(&OnTlsInit, ::_1));
|
||||
|
||||
c.run(uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, hws_map, true, use_itn, svs_itn);
|
||||
} else {
|
||||
WebsocketClient<websocketpp::config::asio_client> c(is_ssl);
|
||||
|
||||
c.run(uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, hws_map, true, use_itn, svs_itn);
|
||||
}
|
||||
|
||||
}else{
|
||||
// read wav_path
|
||||
std::vector<string> wav_list;
|
||||
std::vector<string> wav_ids;
|
||||
string default_id = "wav_default_id";
|
||||
if (funasr::IsTargetFile(wav_path, "scp")) {
|
||||
ifstream in(wav_path);
|
||||
if (!in.is_open()) {
|
||||
printf("Failed to open scp file");
|
||||
return 0;
|
||||
}
|
||||
string line;
|
||||
while (getline(in, line)) {
|
||||
istringstream iss(line);
|
||||
string column1, column2;
|
||||
iss >> column1 >> column2;
|
||||
wav_list.emplace_back(column2);
|
||||
wav_ids.emplace_back(column1);
|
||||
}
|
||||
in.close();
|
||||
} else {
|
||||
wav_list.emplace_back(wav_path);
|
||||
wav_ids.emplace_back(default_id);
|
||||
}
|
||||
|
||||
for (size_t wav_i = 0; wav_i < wav_list.size(); wav_i = wav_i + threads_num) {
|
||||
std::vector<websocketpp::lib::thread> client_threads;
|
||||
for (size_t i = 0; i < threads_num; i++) {
|
||||
if (wav_i + i >= wav_list.size()) {
|
||||
break;
|
||||
}
|
||||
std::vector<string> tmp_wav_list;
|
||||
std::vector<string> tmp_wav_ids;
|
||||
|
||||
tmp_wav_list.emplace_back(wav_list[wav_i + i]);
|
||||
tmp_wav_ids.emplace_back(wav_ids[wav_i + i]);
|
||||
|
||||
client_threads.emplace_back(
|
||||
[uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, is_ssl, hws_map, use_itn, svs_itn]() {
|
||||
if (is_ssl == 1) {
|
||||
WebsocketClient<websocketpp::config::asio_tls_client> c(is_ssl);
|
||||
|
||||
c.m_client.set_tls_init_handler(bind(&OnTlsInit, ::_1));
|
||||
|
||||
c.run(uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, hws_map, false, use_itn, svs_itn);
|
||||
} else {
|
||||
WebsocketClient<websocketpp::config::asio_client> c(is_ssl);
|
||||
|
||||
c.run(uri, tmp_wav_list, tmp_wav_ids, audio_fs, asr_mode, chunk_size, hws_map, false, use_itn, svs_itn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : client_threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaomingwork */
|
||||
|
||||
// client for websocket, support multiple threads
|
||||
// ./funasr-wss-client --server-ip <string>
|
||||
// --port <string>
|
||||
// --wav-path <string>
|
||||
// [--thread-num <int>]
|
||||
// [--is-ssl <int>] [--]
|
||||
// [--version] [-h]
|
||||
// example:
|
||||
// ./funasr-wss-client --server-ip 127.0.0.1 --port 10095 --wav-path test.wav --thread-num 1 --is-ssl 1
|
||||
|
||||
#define ASIO_STANDALONE 1
|
||||
#include <websocketpp/client.hpp>
|
||||
#include <websocketpp/common/thread.hpp>
|
||||
#include <websocketpp/config/asio_client.hpp>
|
||||
#include <fstream>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <glog/logging.h>
|
||||
#include "util.h"
|
||||
#include "audio.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "tclap/CmdLine.h"
|
||||
|
||||
/**
|
||||
* Define a semi-cross platform helper method that waits/sleeps for a bit.
|
||||
*/
|
||||
void WaitABit() {
|
||||
#ifdef WIN32
|
||||
Sleep(200);
|
||||
#else
|
||||
usleep(200);
|
||||
#endif
|
||||
}
|
||||
std::atomic<int> wav_index(0);
|
||||
|
||||
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
|
||||
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context> context_ptr;
|
||||
using websocketpp::lib::bind;
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::placeholders::_2;
|
||||
context_ptr OnTlsInit(websocketpp::connection_hdl) {
|
||||
context_ptr ctx = websocketpp::lib::make_shared<asio::ssl::context>(
|
||||
asio::ssl::context::sslv23);
|
||||
|
||||
try {
|
||||
ctx->set_options(
|
||||
asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 |
|
||||
asio::ssl::context::no_sslv3 | asio::ssl::context::single_dh_use);
|
||||
|
||||
} catch (std::exception& e) {
|
||||
LOG(ERROR) << e.what();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// template for tls or not config
|
||||
template <typename T>
|
||||
class WebsocketClient {
|
||||
public:
|
||||
// typedef websocketpp::client<T> client;
|
||||
// typedef websocketpp::client<websocketpp::config::asio_tls_client>
|
||||
// wss_client;
|
||||
typedef websocketpp::lib::lock_guard<websocketpp::lib::mutex> scoped_lock;
|
||||
|
||||
WebsocketClient(int is_ssl) : m_open(false), m_done(false) {
|
||||
// set up access channels to only log interesting things
|
||||
m_client.clear_access_channels(websocketpp::log::alevel::all);
|
||||
m_client.set_access_channels(websocketpp::log::alevel::connect);
|
||||
m_client.set_access_channels(websocketpp::log::alevel::disconnect);
|
||||
m_client.set_access_channels(websocketpp::log::alevel::app);
|
||||
|
||||
// Initialize the Asio transport policy
|
||||
m_client.init_asio();
|
||||
|
||||
// Bind the handlers we are using
|
||||
using websocketpp::lib::bind;
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
m_client.set_open_handler(bind(&WebsocketClient::on_open, this, _1));
|
||||
m_client.set_close_handler(bind(&WebsocketClient::on_close, this, _1));
|
||||
|
||||
m_client.set_message_handler(
|
||||
[this](websocketpp::connection_hdl hdl, message_ptr msg) {
|
||||
on_message(hdl, msg);
|
||||
});
|
||||
|
||||
m_client.set_fail_handler(bind(&WebsocketClient::on_fail, this, _1));
|
||||
m_client.clear_access_channels(websocketpp::log::alevel::all);
|
||||
}
|
||||
|
||||
void on_message(websocketpp::connection_hdl hdl, message_ptr msg) {
|
||||
const std::string& payload = msg->get_payload();
|
||||
switch (msg->get_opcode()) {
|
||||
case websocketpp::frame::opcode::text:
|
||||
total_recv=total_recv+1;
|
||||
LOG(INFO)<< "Thread: " << this_thread::get_id() << ", total_recv=" << total_recv <<", on_message = " << payload;
|
||||
std::unique_lock<std::mutex> lock(msg_lock);
|
||||
cv.notify_one();
|
||||
if(close_client)
|
||||
{
|
||||
LOG(INFO)<< "Thread: " << this_thread::get_id() << ", close client thread";
|
||||
websocketpp::lib::error_code ec;
|
||||
m_client.close(m_hdl, websocketpp::close::status::going_away, "", ec);
|
||||
if (ec){
|
||||
LOG(ERROR)<< "Error closing connection " << ec.message();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This method will block until the connection is complete
|
||||
void run(const std::string& uri, const std::vector<string>& wav_list, const std::vector<string>& wav_ids,
|
||||
int audio_fs, const std::unordered_map<std::string, int>& hws_map, int use_itn=1, int svs_itn=1) {
|
||||
// Create a new connection to the given URI
|
||||
websocketpp::lib::error_code ec;
|
||||
typename websocketpp::client<T>::connection_ptr con =
|
||||
m_client.get_connection(uri, ec);
|
||||
if (ec) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Get Connection Error: " + ec.message());
|
||||
return;
|
||||
}
|
||||
// Grab a handle for this connection so we can talk to it in a thread
|
||||
// safe manor after the event loop starts.
|
||||
m_hdl = con->get_handle();
|
||||
|
||||
// Queue the connection. No DNS queries or network connections will be
|
||||
// made until the io_service event loop is run.
|
||||
m_client.connect(con);
|
||||
|
||||
// Create a thread to run the ASIO io_service event loop
|
||||
websocketpp::lib::thread asio_thread(&websocketpp::client<T>::run,
|
||||
&m_client);
|
||||
bool send_hotword = true;
|
||||
while(true){
|
||||
int i = wav_index.fetch_add(1);
|
||||
if (i >= wav_list.size()) {
|
||||
break;
|
||||
}
|
||||
if (total_send !=0){
|
||||
std::unique_lock<std::mutex> lock(msg_lock);
|
||||
cv.wait(lock);
|
||||
}
|
||||
total_send += 1;
|
||||
send_wav_data(wav_list[i], wav_ids[i], audio_fs, hws_map, send_hotword, use_itn, svs_itn);
|
||||
if(send_hotword){
|
||||
send_hotword = false;
|
||||
}
|
||||
}
|
||||
close_client = true;
|
||||
asio_thread.join();
|
||||
|
||||
}
|
||||
|
||||
// The open handler will signal that we are ready to start sending data
|
||||
void on_open(websocketpp::connection_hdl) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Connection opened, starting data!");
|
||||
|
||||
scoped_lock guard(m_lock);
|
||||
m_open = true;
|
||||
}
|
||||
|
||||
// The close handler will signal that we should stop sending data
|
||||
void on_close(websocketpp::connection_hdl) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Connection closed, stopping data!");
|
||||
|
||||
scoped_lock guard(m_lock);
|
||||
m_done = true;
|
||||
}
|
||||
|
||||
// The fail handler will signal that we should stop sending data
|
||||
void on_fail(websocketpp::connection_hdl) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Connection failed, stopping data!");
|
||||
|
||||
scoped_lock guard(m_lock);
|
||||
m_done = true;
|
||||
}
|
||||
// send wav to server
|
||||
void send_wav_data(string wav_path, string wav_id, int audio_fs,
|
||||
const std::unordered_map<std::string, int>& hws_map,
|
||||
bool send_hotword, bool use_itn, bool svs_itn) {
|
||||
uint64_t count = 0;
|
||||
std::stringstream val;
|
||||
|
||||
funasr::Audio audio(1);
|
||||
int32_t sampling_rate = audio_fs;
|
||||
std::string wav_format = "pcm";
|
||||
if(funasr::IsTargetFile(wav_path.c_str(), "pcm")){
|
||||
if (!audio.LoadPcmwav(wav_path.c_str(), &sampling_rate, false))
|
||||
return ;
|
||||
}else{
|
||||
wav_format = "others";
|
||||
if (!audio.LoadOthers2Char(wav_path.c_str()))
|
||||
return ;
|
||||
}
|
||||
|
||||
float* buff;
|
||||
int len;
|
||||
int flag = 0;
|
||||
bool wait = false;
|
||||
while (1) {
|
||||
{
|
||||
scoped_lock guard(m_lock);
|
||||
// If the connection has been closed, stop generating data
|
||||
if (m_done) {
|
||||
break;
|
||||
}
|
||||
// If the connection hasn't been opened yet wait a bit and retry
|
||||
if (!m_open) {
|
||||
wait = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (wait) {
|
||||
// LOG(INFO) << "wait.." << m_open;
|
||||
WaitABit();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
websocketpp::lib::error_code ec;
|
||||
|
||||
nlohmann::json jsonbegin;
|
||||
nlohmann::json chunk_size = nlohmann::json::array();
|
||||
chunk_size.push_back(5);
|
||||
chunk_size.push_back(10);
|
||||
chunk_size.push_back(5);
|
||||
jsonbegin["chunk_size"] = chunk_size;
|
||||
jsonbegin["chunk_interval"] = 10;
|
||||
jsonbegin["wav_name"] = wav_id;
|
||||
jsonbegin["wav_format"] = wav_format;
|
||||
jsonbegin["audio_fs"] = sampling_rate;
|
||||
jsonbegin["itn"] = true;
|
||||
jsonbegin["svs_itn"] = true;
|
||||
if(use_itn == 0){
|
||||
jsonbegin["itn"] = false;
|
||||
}
|
||||
if(svs_itn == 0){
|
||||
jsonbegin["svs_itn"] = false;
|
||||
}
|
||||
jsonbegin["is_speaking"] = true;
|
||||
if(send_hotword){
|
||||
if(!hws_map.empty()){
|
||||
LOG(INFO) << "hotwords: ";
|
||||
for (const auto& pair : hws_map) {
|
||||
LOG(INFO) << pair.first << " : " << pair.second;
|
||||
}
|
||||
nlohmann::json json_map(hws_map);
|
||||
std::string json_map_str = json_map.dump();
|
||||
jsonbegin["hotwords"] = json_map_str;
|
||||
}
|
||||
}
|
||||
m_client.send(m_hdl, jsonbegin.dump(), websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
|
||||
// fetch wav data use asr engine api
|
||||
if(wav_format == "pcm"){
|
||||
while (audio.Fetch(buff, len, flag) > 0) {
|
||||
short* iArray = new short[len];
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
iArray[i] = (short)(buff[i]*32768);
|
||||
}
|
||||
|
||||
// send data to server
|
||||
int offset = 0;
|
||||
int block_size = 102400;
|
||||
while(offset < len){
|
||||
int send_block = 0;
|
||||
if (offset + block_size <= len){
|
||||
send_block = block_size;
|
||||
}else{
|
||||
send_block = len - offset;
|
||||
}
|
||||
m_client.send(m_hdl, iArray+offset, send_block * sizeof(short),
|
||||
websocketpp::frame::opcode::binary, ec);
|
||||
offset += send_block;
|
||||
}
|
||||
|
||||
LOG(INFO)<< "Thread: " << this_thread::get_id() << ", sended data len=" << len * sizeof(short);
|
||||
// The most likely error that we will get is that the connection is
|
||||
// not in the right state. Usually this means we tried to send a
|
||||
// message to a connection that was closed or in the process of
|
||||
// closing. While many errors here can be easily recovered from,
|
||||
// in this simple example, we'll stop the data loop.
|
||||
if (ec) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Send Error: " + ec.message());
|
||||
break;
|
||||
}
|
||||
delete[] iArray;
|
||||
// WaitABit();
|
||||
}
|
||||
}else{
|
||||
int offset = 0;
|
||||
int block_size = 204800;
|
||||
len = audio.GetSpeechLen();
|
||||
char* others_buff = audio.GetSpeechChar();
|
||||
|
||||
while(offset < len){
|
||||
int send_block = 0;
|
||||
if (offset + block_size <= len){
|
||||
send_block = block_size;
|
||||
}else{
|
||||
send_block = len - offset;
|
||||
}
|
||||
m_client.send(m_hdl, others_buff+offset, send_block,
|
||||
websocketpp::frame::opcode::binary, ec);
|
||||
offset += send_block;
|
||||
}
|
||||
|
||||
LOG(INFO)<< "Thread: " << this_thread::get_id() << ", sended data len=" << len;
|
||||
// The most likely error that we will get is that the connection is
|
||||
// not in the right state. Usually this means we tried to send a
|
||||
// message to a connection that was closed or in the process of
|
||||
// closing. While many errors here can be easily recovered from,
|
||||
// in this simple example, we'll stop the data loop.
|
||||
if (ec) {
|
||||
m_client.get_alog().write(websocketpp::log::alevel::app,
|
||||
"Send Error: " + ec.message());
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json jsonresult;
|
||||
jsonresult["is_speaking"] = false;
|
||||
m_client.send(m_hdl, jsonresult.dump(), websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
websocketpp::client<T> m_client;
|
||||
|
||||
private:
|
||||
websocketpp::connection_hdl m_hdl;
|
||||
websocketpp::lib::mutex m_lock;
|
||||
websocketpp::lib::mutex msg_lock;
|
||||
websocketpp::lib::condition_variable cv;
|
||||
bool m_open;
|
||||
bool m_done;
|
||||
bool close_client=false;
|
||||
int total_send=0;
|
||||
int total_recv=0;
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
SetConsoleOutputCP(65001);
|
||||
#endif
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
FLAGS_logtostderr = true;
|
||||
|
||||
TCLAP::CmdLine cmd("funasr-wss-client", ' ', "1.0");
|
||||
TCLAP::ValueArg<std::string> server_ip_("", "server-ip", "server-ip", true,
|
||||
"127.0.0.1", "string");
|
||||
TCLAP::ValueArg<std::string> port_("", "port", "port", true, "10095", "string");
|
||||
TCLAP::ValueArg<std::string> wav_path_("", "wav-path",
|
||||
"the input could be: wav_path, e.g.: asr_example.wav; pcm_path, e.g.: asr_example.pcm; wav.scp, kaldi style wav list (wav_id \t wav_path)",
|
||||
true, "", "string");
|
||||
TCLAP::ValueArg<std::int32_t> audio_fs_("", "audio-fs", "the sample rate of audio", false, 16000, "int32_t");
|
||||
TCLAP::ValueArg<int> thread_num_("", "thread-num", "thread-num",
|
||||
false, 1, "int");
|
||||
TCLAP::ValueArg<int> is_ssl_(
|
||||
"", "is-ssl", "is-ssl is 1 means use wss connection, or use ws connection",
|
||||
false, 1, "int");
|
||||
TCLAP::ValueArg<int> use_itn_(
|
||||
"", "use-itn",
|
||||
"use-itn is 1 means use itn, 0 means not use itn", false, 1, "int");
|
||||
TCLAP::ValueArg<int> svs_itn_(
|
||||
"", "svs-itn",
|
||||
"svs-itn is 1 means use itn and punc, 0 means not use", false, 1, "int");
|
||||
TCLAP::ValueArg<std::string> hotword_("", HOTWORD,
|
||||
"the hotword file, one hotword perline, Format: Hotword Weight (could be: 阿里巴巴 20)", false, "", "string");
|
||||
|
||||
cmd.add(server_ip_);
|
||||
cmd.add(port_);
|
||||
cmd.add(wav_path_);
|
||||
cmd.add(audio_fs_);
|
||||
cmd.add(thread_num_);
|
||||
cmd.add(is_ssl_);
|
||||
cmd.add(use_itn_);
|
||||
cmd.add(svs_itn_);
|
||||
cmd.add(hotword_);
|
||||
cmd.parse(argc, argv);
|
||||
|
||||
std::string server_ip = server_ip_.getValue();
|
||||
std::string port = port_.getValue();
|
||||
std::string wav_path = wav_path_.getValue();
|
||||
int threads_num = thread_num_.getValue();
|
||||
int is_ssl = is_ssl_.getValue();
|
||||
int use_itn = use_itn_.getValue();
|
||||
int svs_itn = svs_itn_.getValue();
|
||||
|
||||
std::vector<websocketpp::lib::thread> client_threads;
|
||||
std::string uri = "";
|
||||
if (is_ssl == 1) {
|
||||
uri = "wss://" + server_ip + ":" + port;
|
||||
} else {
|
||||
uri = "ws://" + server_ip + ":" + port;
|
||||
}
|
||||
|
||||
// hotwords
|
||||
std::string hotword_path = hotword_.getValue();
|
||||
unordered_map<string, int> hws_map;
|
||||
if(!hotword_path.empty()){
|
||||
LOG(INFO) << "hotword path: " << hotword_path;
|
||||
funasr::ExtractHws(hotword_path, hws_map);
|
||||
}
|
||||
|
||||
// read wav_path
|
||||
std::vector<string> wav_list;
|
||||
std::vector<string> wav_ids;
|
||||
string default_id = "wav_default_id";
|
||||
if(funasr::IsTargetFile(wav_path, "scp")){
|
||||
ifstream in(wav_path);
|
||||
if (!in.is_open()) {
|
||||
printf("Failed to open scp file");
|
||||
return 0;
|
||||
}
|
||||
string line;
|
||||
while(getline(in, line))
|
||||
{
|
||||
istringstream iss(line);
|
||||
string column1, column2;
|
||||
iss >> column1 >> column2;
|
||||
wav_list.emplace_back(column2);
|
||||
wav_ids.emplace_back(column1);
|
||||
}
|
||||
in.close();
|
||||
}else{
|
||||
wav_list.emplace_back(wav_path);
|
||||
wav_ids.emplace_back(default_id);
|
||||
}
|
||||
|
||||
int audio_fs = audio_fs_.getValue();
|
||||
for (size_t i = 0; i < threads_num; i++) {
|
||||
client_threads.emplace_back([uri, wav_list, wav_ids, audio_fs, is_ssl, hws_map, use_itn, svs_itn]() {
|
||||
if (is_ssl == 1) {
|
||||
WebsocketClient<websocketpp::config::asio_tls_client> c(is_ssl);
|
||||
|
||||
c.m_client.set_tls_init_handler(bind(&OnTlsInit, ::_1));
|
||||
|
||||
c.run(uri, wav_list, wav_ids, audio_fs, hws_map, use_itn, svs_itn);
|
||||
} else {
|
||||
WebsocketClient<websocketpp::config::asio_client> c(is_ssl);
|
||||
|
||||
c.run(uri, wav_list, wav_ids, audio_fs, hws_map, use_itn, svs_itn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : client_threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaomingwork */
|
||||
|
||||
#include "websocket-server-2pass.h"
|
||||
#ifdef _WIN32
|
||||
#include "win_func.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <fstream>
|
||||
#include "util.h"
|
||||
|
||||
// hotwords
|
||||
std::unordered_map<std::string, int> hws_map_;
|
||||
int fst_inc_wts_=20;
|
||||
float global_beam_, lattice_beam_, am_scale_;
|
||||
|
||||
using namespace std;
|
||||
void GetValue(TCLAP::ValueArg<std::string>& value_arg, string key,
|
||||
std::map<std::string, std::string>& model_path) {
|
||||
model_path.insert({key, value_arg.getValue()});
|
||||
LOG(INFO) << key << " : " << value_arg.getValue();
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
SetConsoleOutputCP(65001);
|
||||
#endif
|
||||
try {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
FLAGS_logtostderr = true;
|
||||
std::string tpass_version = "";
|
||||
#ifdef _WIN32
|
||||
tpass_version = "0.1.0";
|
||||
#endif
|
||||
TCLAP::CmdLine cmd("funasr-wss-server", ' ', tpass_version);
|
||||
TCLAP::ValueArg<std::string> download_model_dir(
|
||||
"", "download-model-dir",
|
||||
"Download model from Modelscope to download_model_dir", false,
|
||||
"/workspace/models", "string");
|
||||
TCLAP::ValueArg<std::string> offline_model_dir(
|
||||
"", OFFLINE_MODEL_DIR,
|
||||
"default: damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx, the asr model path, which "
|
||||
"contains model_quant.onnx, config.yaml, am.mvn",
|
||||
false, "damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx", "string");
|
||||
TCLAP::ValueArg<std::string> online_model_dir(
|
||||
"", ONLINE_MODEL_DIR,
|
||||
"default: damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx, the asr model path, which "
|
||||
"contains model_quant.onnx, config.yaml, am.mvn",
|
||||
false, "damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx", "string");
|
||||
|
||||
TCLAP::ValueArg<std::string> offline_model_revision(
|
||||
"", "offline-model-revision", "ASR offline model revision", false,
|
||||
"v2.0.5", "string");
|
||||
|
||||
TCLAP::ValueArg<std::string> online_model_revision(
|
||||
"", "online-model-revision", "ASR online model revision", false,
|
||||
"v2.0.5", "string");
|
||||
|
||||
TCLAP::ValueArg<std::string> quantize(
|
||||
"", QUANTIZE,
|
||||
"true (Default), load the model of model_quant.onnx in model_dir. If "
|
||||
"set "
|
||||
"false, load the model of model.onnx in model_dir",
|
||||
false, "true", "string");
|
||||
TCLAP::ValueArg<std::string> vad_dir(
|
||||
"", VAD_DIR,
|
||||
"default: damo/speech_fsmn_vad_zh-cn-16k-common-onnx, the vad model path, which contains "
|
||||
"model_quant.onnx, vad.yaml, vad.mvn",
|
||||
false, "damo/speech_fsmn_vad_zh-cn-16k-common-onnx", "string");
|
||||
TCLAP::ValueArg<std::string> vad_revision(
|
||||
"", "vad-revision", "VAD model revision", false, "v2.0.4", "string");
|
||||
TCLAP::ValueArg<std::string> vad_quant(
|
||||
"", VAD_QUANT,
|
||||
"true (Default), load the model of model_quant.onnx in vad_dir. If set "
|
||||
"false, load the model of model.onnx in vad_dir",
|
||||
false, "true", "string");
|
||||
TCLAP::ValueArg<std::string> punc_dir(
|
||||
"", PUNC_DIR,
|
||||
"default: damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727-onnx, the punc model path, which contains "
|
||||
"model_quant.onnx, punc.yaml",
|
||||
false, "damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727-onnx", "string");
|
||||
TCLAP::ValueArg<std::string> punc_revision(
|
||||
"", "punc-revision", "PUNC model revision", false, "v2.0.5", "string");
|
||||
TCLAP::ValueArg<std::string> punc_quant(
|
||||
"", PUNC_QUANT,
|
||||
"true (Default), load the model of model_quant.onnx in punc_dir. If "
|
||||
"set "
|
||||
"false, load the model of model.onnx in punc_dir",
|
||||
false, "true", "string");
|
||||
TCLAP::ValueArg<std::string> itn_dir(
|
||||
"", ITN_DIR,
|
||||
"default: thuduj12/fst_itn_zh, the itn model path, which contains "
|
||||
"zh_itn_tagger.fst, zh_itn_verbalizer.fst",
|
||||
false, "thuduj12/fst_itn_zh", "string");
|
||||
TCLAP::ValueArg<std::string> itn_revision(
|
||||
"", "itn-revision", "ITN model revision", false, "v1.0.1", "string");
|
||||
|
||||
TCLAP::ValueArg<std::string> listen_ip("", "listen-ip", "listen ip", false,
|
||||
"0.0.0.0", "string");
|
||||
TCLAP::ValueArg<int> port("", "port", "port", false, 10095, "int");
|
||||
TCLAP::ValueArg<int> io_thread_num("", "io-thread-num", "io thread num",
|
||||
false, 2, "int");
|
||||
TCLAP::ValueArg<int> decoder_thread_num(
|
||||
"", "decoder-thread-num", "decoder thread num", false, 8, "int");
|
||||
TCLAP::ValueArg<int> model_thread_num("", "model-thread-num",
|
||||
"model thread num", false, 2, "int");
|
||||
|
||||
TCLAP::ValueArg<std::string> certfile(
|
||||
"", "certfile",
|
||||
"default: ../../../ssl_key/server.crt, path of certficate for WSS "
|
||||
"connection. if it is empty, it will be in WS mode.",
|
||||
false, "../../../ssl_key/server.crt", "string");
|
||||
TCLAP::ValueArg<std::string> keyfile(
|
||||
"", "keyfile",
|
||||
"default: ../../../ssl_key/server.key, path of keyfile for WSS "
|
||||
"connection",
|
||||
false, "../../../ssl_key/server.key", "string");
|
||||
|
||||
TCLAP::ValueArg<float> global_beam("", GLOB_BEAM, "the decoding beam for beam searching ", false, 3.0, "float");
|
||||
TCLAP::ValueArg<float> lattice_beam("", LAT_BEAM, "the lattice generation beam for beam searching ", false, 3.0, "float");
|
||||
TCLAP::ValueArg<float> am_scale("", AM_SCALE, "the acoustic scale for beam searching ", false, 10.0, "float");
|
||||
|
||||
TCLAP::ValueArg<std::string> lm_dir("", LM_DIR,
|
||||
"the LM model path, which contains compiled models: TLG.fst, config.yaml ", false, "damo/speech_ngram_lm_zh-cn-ai-wesp-fst", "string");
|
||||
TCLAP::ValueArg<std::string> lm_revision(
|
||||
"", "lm-revision", "LM model revision", false, "v1.0.2", "string");
|
||||
TCLAP::ValueArg<std::string> hotword("", HOTWORD,
|
||||
"the hotword file, one hotword perline, Format: Hotword Weight (could be: 阿里巴巴 20)",
|
||||
false, "/workspace/resources/hotwords.txt", "string");
|
||||
TCLAP::ValueArg<std::int32_t> fst_inc_wts("", FST_INC_WTS,
|
||||
"the fst hotwords incremental bias", false, 20, "int32_t");
|
||||
|
||||
// add file
|
||||
cmd.add(hotword);
|
||||
cmd.add(fst_inc_wts);
|
||||
cmd.add(global_beam);
|
||||
cmd.add(lattice_beam);
|
||||
cmd.add(am_scale);
|
||||
|
||||
cmd.add(certfile);
|
||||
cmd.add(keyfile);
|
||||
|
||||
cmd.add(download_model_dir);
|
||||
cmd.add(offline_model_dir);
|
||||
cmd.add(online_model_dir);
|
||||
cmd.add(offline_model_revision);
|
||||
cmd.add(online_model_revision);
|
||||
cmd.add(quantize);
|
||||
cmd.add(vad_dir);
|
||||
cmd.add(vad_revision);
|
||||
cmd.add(vad_quant);
|
||||
cmd.add(punc_dir);
|
||||
cmd.add(punc_revision);
|
||||
cmd.add(punc_quant);
|
||||
cmd.add(itn_dir);
|
||||
cmd.add(itn_revision);
|
||||
cmd.add(lm_dir);
|
||||
cmd.add(lm_revision);
|
||||
|
||||
cmd.add(listen_ip);
|
||||
cmd.add(port);
|
||||
cmd.add(io_thread_num);
|
||||
cmd.add(decoder_thread_num);
|
||||
cmd.add(model_thread_num);
|
||||
cmd.parse(argc, argv);
|
||||
|
||||
std::map<std::string, std::string> model_path;
|
||||
GetValue(offline_model_dir, OFFLINE_MODEL_DIR, model_path);
|
||||
GetValue(online_model_dir, ONLINE_MODEL_DIR, model_path);
|
||||
GetValue(quantize, QUANTIZE, model_path);
|
||||
GetValue(vad_dir, VAD_DIR, model_path);
|
||||
GetValue(vad_quant, VAD_QUANT, model_path);
|
||||
GetValue(punc_dir, PUNC_DIR, model_path);
|
||||
GetValue(punc_quant, PUNC_QUANT, model_path);
|
||||
GetValue(itn_dir, ITN_DIR, model_path);
|
||||
GetValue(lm_dir, LM_DIR, model_path);
|
||||
GetValue(hotword, HOTWORD, model_path);
|
||||
|
||||
GetValue(offline_model_revision, "offline-model-revision", model_path);
|
||||
GetValue(online_model_revision, "online-model-revision", model_path);
|
||||
GetValue(vad_revision, "vad-revision", model_path);
|
||||
GetValue(punc_revision, "punc-revision", model_path);
|
||||
GetValue(itn_revision, "itn-revision", model_path);
|
||||
GetValue(lm_revision, "lm-revision", model_path);
|
||||
|
||||
global_beam_ = global_beam.getValue();
|
||||
lattice_beam_ = lattice_beam.getValue();
|
||||
am_scale_ = am_scale.getValue();
|
||||
|
||||
// Download model form Modelscope
|
||||
try {
|
||||
std::string s_download_model_dir = download_model_dir.getValue();
|
||||
|
||||
std::string s_vad_path = model_path[VAD_DIR];
|
||||
std::string s_vad_quant = model_path[VAD_QUANT];
|
||||
std::string s_offline_asr_path = model_path[OFFLINE_MODEL_DIR];
|
||||
std::string s_online_asr_path = model_path[ONLINE_MODEL_DIR];
|
||||
std::string s_asr_quant = model_path[QUANTIZE];
|
||||
std::string s_punc_path = model_path[PUNC_DIR];
|
||||
std::string s_punc_quant = model_path[PUNC_QUANT];
|
||||
std::string s_itn_path = model_path[ITN_DIR];
|
||||
std::string s_lm_path = model_path[LM_DIR];
|
||||
|
||||
std::string python_cmd =
|
||||
"python -m funasr.download.runtime_sdk_download_tool --type onnx --quantize True ";
|
||||
|
||||
if (!s_vad_path.empty()) {
|
||||
std::string python_cmd_vad;
|
||||
std::string down_vad_path;
|
||||
std::string down_vad_model;
|
||||
|
||||
if (access(s_vad_path.c_str(), F_OK) == 0) {
|
||||
// local
|
||||
python_cmd_vad = python_cmd + " --model-name " + s_vad_path +
|
||||
" --export-dir ./ " + " --model_revision " +
|
||||
model_path["vad-revision"];
|
||||
down_vad_path = s_vad_path;
|
||||
} else {
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_vad_path
|
||||
<< " from modelscope: ";
|
||||
python_cmd_vad = python_cmd + " --model-name " +
|
||||
s_vad_path +
|
||||
" --export-dir " + s_download_model_dir +
|
||||
" --model_revision " + model_path["vad-revision"];
|
||||
down_vad_path =
|
||||
s_download_model_dir +
|
||||
"/" + s_vad_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_vad.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local vad model path, you can ignore the errors.";
|
||||
}
|
||||
down_vad_model = down_vad_path + "/model_quant.onnx";
|
||||
if (s_vad_quant == "false" || s_vad_quant == "False" ||
|
||||
s_vad_quant == "FALSE") {
|
||||
down_vad_model = down_vad_path + "/model.onnx";
|
||||
}
|
||||
|
||||
if (access(down_vad_model.c_str(), F_OK) != 0) {
|
||||
LOG(ERROR) << down_vad_model << " do not exists.";
|
||||
exit(-1);
|
||||
} else {
|
||||
model_path[VAD_DIR] = down_vad_path;
|
||||
LOG(INFO) << "Set " << VAD_DIR << " : " << model_path[VAD_DIR];
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG(INFO) << "VAD model is not set, use default.";
|
||||
}
|
||||
|
||||
if (!s_offline_asr_path.empty()) {
|
||||
std::string python_cmd_asr;
|
||||
std::string down_asr_path;
|
||||
std::string down_asr_model;
|
||||
|
||||
size_t found = s_offline_asr_path.find("speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404");
|
||||
if (found != std::string::npos) {
|
||||
model_path["offline-model-revision"]="v2.0.5";
|
||||
}
|
||||
|
||||
found = s_offline_asr_path.find("speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404");
|
||||
if (found != std::string::npos) {
|
||||
model_path["offline-model-revision"]="v2.0.5";
|
||||
}
|
||||
|
||||
found = s_offline_asr_path.find("speech_paraformer-large_asr_nat-en-16k-common-vocab10020");
|
||||
if (found != std::string::npos) {
|
||||
model_path["model-revision"]="v2.0.5";
|
||||
s_itn_path="";
|
||||
s_lm_path="";
|
||||
}
|
||||
found = s_offline_asr_path.find(MODEL_SVS);
|
||||
if (found != std::string::npos) {
|
||||
model_path["model-revision"]="v2.0.5";
|
||||
s_lm_path="";
|
||||
model_path[LM_DIR]="";
|
||||
}
|
||||
|
||||
if (access(s_offline_asr_path.c_str(), F_OK) == 0) {
|
||||
// local
|
||||
python_cmd_asr = python_cmd + " --model-name " + s_offline_asr_path +
|
||||
" --export-dir ./ " + " --model_revision " +
|
||||
model_path["offline-model-revision"];
|
||||
down_asr_path = s_offline_asr_path;
|
||||
}
|
||||
else {
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_offline_asr_path
|
||||
<< " from modelscope : ";
|
||||
python_cmd_asr = python_cmd + " --model-name " +
|
||||
s_offline_asr_path +
|
||||
" --export-dir " + s_download_model_dir +
|
||||
" --model_revision " + model_path["offline-model-revision"];
|
||||
down_asr_path
|
||||
= s_download_model_dir + "/" + s_offline_asr_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_asr.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local asr model path, you can ignore the errors.";
|
||||
}
|
||||
down_asr_model = down_asr_path + "/model_quant.onnx";
|
||||
if (s_asr_quant == "false" || s_asr_quant == "False" ||
|
||||
s_asr_quant == "FALSE") {
|
||||
down_asr_model = down_asr_path + "/model.onnx";
|
||||
}
|
||||
|
||||
if (access(down_asr_model.c_str(), F_OK) != 0) {
|
||||
LOG(ERROR) << down_asr_model << " do not exists.";
|
||||
exit(-1);
|
||||
} else {
|
||||
model_path[OFFLINE_MODEL_DIR] = down_asr_path;
|
||||
LOG(INFO) << "Set " << OFFLINE_MODEL_DIR << " : " << model_path[OFFLINE_MODEL_DIR];
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "ASR Offline model is not set, use default.";
|
||||
}
|
||||
|
||||
if (!s_online_asr_path.empty()) {
|
||||
std::string python_cmd_asr;
|
||||
std::string down_asr_path;
|
||||
std::string down_asr_model;
|
||||
|
||||
if (access(s_online_asr_path.c_str(), F_OK) == 0) {
|
||||
// local
|
||||
python_cmd_asr = python_cmd + " --model-name " + s_online_asr_path +
|
||||
" --export-dir ./ " + " --model_revision " +
|
||||
model_path["online-model-revision"];
|
||||
down_asr_path = s_online_asr_path;
|
||||
} else {
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_online_asr_path
|
||||
<< " from modelscope : ";
|
||||
python_cmd_asr = python_cmd + " --model-name " +
|
||||
s_online_asr_path +
|
||||
" --export-dir " + s_download_model_dir +
|
||||
" --model_revision " + model_path["online-model-revision"];
|
||||
down_asr_path
|
||||
= s_download_model_dir + "/" + s_online_asr_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_asr.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local asr model path, you can ignore the errors.";
|
||||
}
|
||||
down_asr_model = down_asr_path + "/model_quant.onnx";
|
||||
if (s_asr_quant == "false" || s_asr_quant == "False" ||
|
||||
s_asr_quant == "FALSE") {
|
||||
down_asr_model = down_asr_path + "/model.onnx";
|
||||
}
|
||||
|
||||
if (access(down_asr_model.c_str(), F_OK) != 0) {
|
||||
LOG(ERROR) << down_asr_model << " do not exists.";
|
||||
exit(-1);
|
||||
} else {
|
||||
model_path[ONLINE_MODEL_DIR] = down_asr_path;
|
||||
LOG(INFO) << "Set " << ONLINE_MODEL_DIR << " : " << model_path[ONLINE_MODEL_DIR];
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "ASR online model is not set, use default.";
|
||||
}
|
||||
|
||||
if (!s_lm_path.empty() && s_lm_path != "NONE" && s_lm_path != "none") {
|
||||
std::string python_cmd_lm;
|
||||
std::string down_lm_path;
|
||||
std::string down_lm_model;
|
||||
|
||||
if (access(s_lm_path.c_str(), F_OK) == 0) {
|
||||
// local
|
||||
python_cmd_lm = python_cmd + " --model-name " + s_lm_path +
|
||||
" --export-dir ./ " + " --model_revision " +
|
||||
model_path["lm-revision"] + " --export False ";
|
||||
down_lm_path = s_lm_path;
|
||||
} else {
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_lm_path
|
||||
<< " from modelscope : ";
|
||||
python_cmd_lm = python_cmd + " --model-name " +
|
||||
s_lm_path +
|
||||
" --export-dir " + s_download_model_dir +
|
||||
" --model_revision " + model_path["lm-revision"]
|
||||
+ " --export False ";
|
||||
down_lm_path =
|
||||
s_download_model_dir +
|
||||
"/" + s_lm_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_lm.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local lm model path, you can ignore the errors.";
|
||||
}
|
||||
down_lm_model = down_lm_path + "/TLG.fst";
|
||||
|
||||
if (access(down_lm_model.c_str(), F_OK) != 0) {
|
||||
LOG(ERROR) << down_lm_model << " do not exists.";
|
||||
exit(-1);
|
||||
} else {
|
||||
model_path[LM_DIR] = down_lm_path;
|
||||
LOG(INFO) << "Set " << LM_DIR << " : " << model_path[LM_DIR];
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "LM model is not set, not executed.";
|
||||
model_path[LM_DIR] = "";
|
||||
}
|
||||
|
||||
if (!s_punc_path.empty()) {
|
||||
std::string python_cmd_punc;
|
||||
std::string down_punc_path;
|
||||
std::string down_punc_model;
|
||||
|
||||
if (access(s_punc_path.c_str(), F_OK) == 0) {
|
||||
// local
|
||||
python_cmd_punc = python_cmd + " --model-name " + s_punc_path +
|
||||
" --export-dir ./ " + " --model_revision " +
|
||||
model_path["punc-revision"];
|
||||
down_punc_path = s_punc_path;
|
||||
} else {
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_punc_path
|
||||
<< " from modelscope : ";
|
||||
python_cmd_punc = python_cmd + " --model-name " +
|
||||
s_punc_path +
|
||||
" --export-dir " + s_download_model_dir +
|
||||
" --model_revision " + model_path["punc-revision"];
|
||||
down_punc_path =
|
||||
s_download_model_dir +
|
||||
"/" + s_punc_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_punc.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local punc model path, you can ignore the errors.";
|
||||
}
|
||||
down_punc_model = down_punc_path + "/model_quant.onnx";
|
||||
if (s_punc_quant == "false" || s_punc_quant == "False" ||
|
||||
s_punc_quant == "FALSE") {
|
||||
down_punc_model = down_punc_path + "/model.onnx";
|
||||
}
|
||||
|
||||
if (access(down_punc_model.c_str(), F_OK) != 0) {
|
||||
LOG(ERROR) << down_punc_model << " do not exists.";
|
||||
exit(-1);
|
||||
} else {
|
||||
model_path[PUNC_DIR] = down_punc_path;
|
||||
LOG(INFO) << "Set " << PUNC_DIR << " : " << model_path[PUNC_DIR];
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "PUNC model is not set, use default.";
|
||||
}
|
||||
|
||||
if (!s_itn_path.empty()) {
|
||||
std::string python_cmd_itn;
|
||||
std::string down_itn_path;
|
||||
std::string down_itn_model;
|
||||
|
||||
if (access(s_itn_path.c_str(), F_OK) == 0) {
|
||||
// local
|
||||
python_cmd_itn = python_cmd + " --model-name " + s_itn_path +
|
||||
" --export-dir ./ " + " --model_revision " +
|
||||
model_path["itn-revision"] + " --export False ";
|
||||
down_itn_path = s_itn_path;
|
||||
} else {
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_itn_path
|
||||
<< " from modelscope : ";
|
||||
python_cmd_itn = python_cmd + " --model-name " +
|
||||
s_itn_path +
|
||||
" --export-dir " + s_download_model_dir +
|
||||
" --model_revision " + model_path["itn-revision"]
|
||||
+ " --export False ";
|
||||
down_itn_path =
|
||||
s_download_model_dir +
|
||||
"/" + s_itn_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_itn.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local itn model path, you can ignore the errors.";
|
||||
}
|
||||
down_itn_model = down_itn_path + "/zh_itn_tagger.fst";
|
||||
|
||||
if (access(down_itn_model.c_str(), F_OK) != 0) {
|
||||
LOG(ERROR) << down_itn_model << " do not exists.";
|
||||
exit(-1);
|
||||
} else {
|
||||
model_path[ITN_DIR] = down_itn_path;
|
||||
LOG(INFO) << "Set " << ITN_DIR << " : " << model_path[ITN_DIR];
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "ITN model is not set, use default.";
|
||||
}
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
LOG(ERROR) << "Error: " << e.what();
|
||||
}
|
||||
|
||||
std::string s_listen_ip = listen_ip.getValue();
|
||||
int s_port = port.getValue();
|
||||
int s_io_thread_num = io_thread_num.getValue();
|
||||
int s_decoder_thread_num = decoder_thread_num.getValue();
|
||||
|
||||
int s_model_thread_num = model_thread_num.getValue();
|
||||
|
||||
asio::io_context io_decoder; // context for decoding
|
||||
asio::io_context io_server; // context for server
|
||||
|
||||
std::vector<std::thread> decoder_threads;
|
||||
|
||||
std::string s_certfile = certfile.getValue();
|
||||
std::string s_keyfile = keyfile.getValue();
|
||||
|
||||
// hotword file
|
||||
std::string hotword_path;
|
||||
hotword_path = model_path.at(HOTWORD);
|
||||
fst_inc_wts_ = fst_inc_wts.getValue();
|
||||
LOG(INFO) << "hotword path: " << hotword_path;
|
||||
funasr::ExtractHws(hotword_path, hws_map_);
|
||||
|
||||
bool is_ssl = false;
|
||||
if (!s_certfile.empty() && access(s_certfile.c_str(), F_OK) == 0) {
|
||||
is_ssl = true;
|
||||
}
|
||||
|
||||
auto conn_guard = asio::make_work_guard(
|
||||
io_decoder); // make sure threads can wait in the queue
|
||||
auto server_guard = asio::make_work_guard(
|
||||
io_server); // make sure threads can wait in the queue
|
||||
// create threads pool
|
||||
for (int32_t i = 0; i < s_decoder_thread_num; ++i) {
|
||||
decoder_threads.emplace_back([&io_decoder]() { io_decoder.run(); });
|
||||
}
|
||||
|
||||
server server_; // server for websocket
|
||||
wss_server wss_server_;
|
||||
server* server = nullptr;
|
||||
wss_server* wss_server = nullptr;
|
||||
if (is_ssl) {
|
||||
LOG(INFO)<< "SSL is opened!";
|
||||
wss_server_.init_asio(&io_server); // init asio
|
||||
wss_server_.set_reuse_addr(
|
||||
true); // reuse address as we create multiple threads
|
||||
|
||||
// list on port for accept
|
||||
wss_server_.listen(asio::ip::address::from_string(s_listen_ip), s_port);
|
||||
wss_server = &wss_server_;
|
||||
|
||||
} else {
|
||||
LOG(INFO)<< "SSL is closed!";
|
||||
server_.init_asio(&io_server); // init asio
|
||||
server_.set_reuse_addr(
|
||||
true); // reuse address as we create multiple threads
|
||||
|
||||
// list on port for accept
|
||||
server_.listen(asio::ip::address::from_string(s_listen_ip), s_port);
|
||||
server = &server_;
|
||||
|
||||
}
|
||||
|
||||
WebSocketServer websocket_srv(
|
||||
io_decoder, is_ssl, server, wss_server, s_certfile,
|
||||
s_keyfile); // websocket server for asr engine
|
||||
websocket_srv.initAsr(model_path, s_model_thread_num); // init asr model
|
||||
|
||||
LOG(INFO) << "decoder-thread-num: " << s_decoder_thread_num;
|
||||
LOG(INFO) << "io-thread-num: " << s_io_thread_num;
|
||||
LOG(INFO) << "model-thread-num: " << s_model_thread_num;
|
||||
LOG(INFO) << "asr model init finished. listen on port:" << s_port;
|
||||
|
||||
// Start the ASIO network io_service run loop
|
||||
std::vector<std::thread> ts;
|
||||
// create threads for io network
|
||||
for (size_t i = 0; i < s_io_thread_num; i++) {
|
||||
ts.emplace_back([&io_server]() { io_server.run(); });
|
||||
}
|
||||
// wait for theads
|
||||
for (size_t i = 0; i < s_io_thread_num; i++) {
|
||||
ts[i].join();
|
||||
}
|
||||
|
||||
// wait for theads
|
||||
for (auto& t : decoder_threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
LOG(ERROR) << "Error: " << e.what();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaomingwork */
|
||||
|
||||
#include "websocket-server.h"
|
||||
#ifdef _WIN32
|
||||
#include "win_func.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <fstream>
|
||||
#include "util.h"
|
||||
|
||||
// hotwords
|
||||
std::unordered_map<std::string, int> hws_map_;
|
||||
int fst_inc_wts_=20;
|
||||
float global_beam_, lattice_beam_, am_scale_;
|
||||
|
||||
using namespace std;
|
||||
void GetValue(TCLAP::ValueArg<std::string>& value_arg, string key,
|
||||
std::map<std::string, std::string>& model_path) {
|
||||
model_path.insert({key, value_arg.getValue()});
|
||||
LOG(INFO) << key << " : " << value_arg.getValue();
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
SetConsoleOutputCP(65001);
|
||||
#endif
|
||||
try {
|
||||
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
FLAGS_logtostderr = true;
|
||||
std::string offline_version = "";
|
||||
#ifdef _WIN32
|
||||
offline_version = "0.1.0";
|
||||
#endif
|
||||
TCLAP::CmdLine cmd("funasr-wss-server", ' ', offline_version);
|
||||
TCLAP::ValueArg<std::string> download_model_dir(
|
||||
"", "download-model-dir",
|
||||
"Download model from Modelscope to download_model_dir",
|
||||
false, "/workspace/models", "string");
|
||||
TCLAP::ValueArg<std::string> model_dir(
|
||||
"", MODEL_DIR,
|
||||
"default: /workspace/models/asr, the asr model path, which contains *.onnx/*.torchscript, config.yaml, am.mvn",
|
||||
false, "/workspace/models/asr", "string");
|
||||
TCLAP::ValueArg<std::string> model_revision(
|
||||
"", "model-revision",
|
||||
"ASR model revision",
|
||||
false, "v2.0.5", "string");
|
||||
TCLAP::ValueArg<std::string> quantize(
|
||||
"", QUANTIZE,
|
||||
"true (Default), load the model of model_quant.onnx in model_dir. If set "
|
||||
"false, load the model of model.onnx in model_dir",
|
||||
false, "true", "string");
|
||||
TCLAP::ValueArg<std::string> bladedisc(
|
||||
"", BLADEDISC,
|
||||
"true (Default), load the model of bladedisc in model_dir.",
|
||||
false, "true", "string");
|
||||
TCLAP::ValueArg<std::string> vad_dir(
|
||||
"", VAD_DIR,
|
||||
"default: /workspace/models/vad, the vad model path, which contains model_quant.onnx, vad.yaml, vad.mvn",
|
||||
false, "/workspace/models/vad", "string");
|
||||
TCLAP::ValueArg<std::string> vad_revision(
|
||||
"", "vad-revision",
|
||||
"VAD model revision",
|
||||
false, "v2.0.6", "string");
|
||||
TCLAP::ValueArg<std::string> vad_quant(
|
||||
"", VAD_QUANT,
|
||||
"true (Default), load the model of model_quant.onnx in vad_dir. If set "
|
||||
"false, load the model of model.onnx in vad_dir",
|
||||
false, "true", "string");
|
||||
TCLAP::ValueArg<std::string> punc_dir(
|
||||
"", PUNC_DIR,
|
||||
"default: /workspace/models/punc, the punc model path, which contains model_quant.onnx, punc.yaml",
|
||||
false, "/workspace/models/punc",
|
||||
"string");
|
||||
TCLAP::ValueArg<std::string> punc_revision(
|
||||
"", "punc-revision",
|
||||
"PUNC model revision",
|
||||
false, "v2.0.5", "string");
|
||||
TCLAP::ValueArg<std::string> punc_quant(
|
||||
"", PUNC_QUANT,
|
||||
"true (Default), load the model of model_quant.onnx in punc_dir. If set "
|
||||
"false, load the model of model.onnx in punc_dir",
|
||||
false, "true", "string");
|
||||
TCLAP::ValueArg<std::string> itn_dir(
|
||||
"", ITN_DIR,
|
||||
"default: thuduj12/fst_itn_zh, the itn model path, which contains "
|
||||
"zh_itn_tagger.fst, zh_itn_verbalizer.fst",
|
||||
false, "thuduj12/fst_itn_zh", "string");
|
||||
TCLAP::ValueArg<std::string> itn_revision(
|
||||
"", "itn-revision", "ITN model revision", false, "v1.0.1", "string");
|
||||
|
||||
TCLAP::ValueArg<std::string> listen_ip("", "listen-ip", "listen ip", false,
|
||||
"0.0.0.0", "string");
|
||||
TCLAP::ValueArg<int> port("", "port", "port", false, 10095, "int");
|
||||
TCLAP::ValueArg<int> io_thread_num("", "io-thread-num", "io thread num",
|
||||
false, 2, "int");
|
||||
TCLAP::ValueArg<int> decoder_thread_num(
|
||||
"", "decoder-thread-num", "decoder thread num", false, 8, "int");
|
||||
TCLAP::ValueArg<int> model_thread_num("", "model-thread-num",
|
||||
"model thread num", false, 1, "int");
|
||||
|
||||
TCLAP::ValueArg<std::string> certfile("", "certfile",
|
||||
"default: ../../../ssl_key/server.crt, path of certficate for WSS connection. if it is empty, it will be in WS mode.",
|
||||
false, "../../../ssl_key/server.crt", "string");
|
||||
TCLAP::ValueArg<std::string> keyfile("", "keyfile",
|
||||
"default: ../../../ssl_key/server.key, path of keyfile for WSS connection",
|
||||
false, "../../../ssl_key/server.key", "string");
|
||||
|
||||
TCLAP::ValueArg<float> global_beam("", GLOB_BEAM, "the decoding beam for beam searching ", false, 3.0, "float");
|
||||
TCLAP::ValueArg<float> lattice_beam("", LAT_BEAM, "the lattice generation beam for beam searching ", false, 3.0, "float");
|
||||
TCLAP::ValueArg<float> am_scale("", AM_SCALE, "the acoustic scale for beam searching ", false, 10.0, "float");
|
||||
|
||||
TCLAP::ValueArg<std::string> lm_dir("", LM_DIR,
|
||||
"the LM model path, which contains compiled models: TLG.fst, config.yaml ", false, "damo/speech_ngram_lm_zh-cn-ai-wesp-fst", "string");
|
||||
TCLAP::ValueArg<std::string> lm_revision(
|
||||
"", "lm-revision", "LM model revision", false, "v1.0.2", "string");
|
||||
TCLAP::ValueArg<std::string> hotword("", HOTWORD,
|
||||
"the hotword file, one hotword perline, Format: Hotword Weight (could be: 阿里巴巴 20)",
|
||||
false, "/workspace/resources/hotwords.txt", "string");
|
||||
TCLAP::ValueArg<std::int32_t> fst_inc_wts("", FST_INC_WTS,
|
||||
"the fst hotwords incremental bias", false, 20, "int32_t");
|
||||
TCLAP::SwitchArg use_gpu("", INFER_GPU, "Whether to use GPU, default is false", false);
|
||||
TCLAP::ValueArg<std::int32_t> batch_size("", BATCHSIZE, "batch_size for ASR model when using GPU", false, 4, "int32_t");
|
||||
|
||||
// add file
|
||||
cmd.add(hotword);
|
||||
cmd.add(fst_inc_wts);
|
||||
cmd.add(global_beam);
|
||||
cmd.add(lattice_beam);
|
||||
cmd.add(am_scale);
|
||||
|
||||
cmd.add(certfile);
|
||||
cmd.add(keyfile);
|
||||
cmd.add(download_model_dir);
|
||||
cmd.add(model_dir);
|
||||
cmd.add(model_revision);
|
||||
cmd.add(quantize);
|
||||
cmd.add(bladedisc);
|
||||
cmd.add(vad_dir);
|
||||
cmd.add(vad_revision);
|
||||
cmd.add(vad_quant);
|
||||
cmd.add(punc_dir);
|
||||
cmd.add(punc_revision);
|
||||
cmd.add(punc_quant);
|
||||
cmd.add(itn_dir);
|
||||
cmd.add(itn_revision);
|
||||
cmd.add(lm_dir);
|
||||
cmd.add(lm_revision);
|
||||
|
||||
cmd.add(listen_ip);
|
||||
cmd.add(port);
|
||||
cmd.add(io_thread_num);
|
||||
cmd.add(decoder_thread_num);
|
||||
cmd.add(model_thread_num);
|
||||
cmd.add(use_gpu);
|
||||
cmd.add(batch_size);
|
||||
cmd.parse(argc, argv);
|
||||
|
||||
std::map<std::string, std::string> model_path;
|
||||
GetValue(model_dir, MODEL_DIR, model_path);
|
||||
GetValue(quantize, QUANTIZE, model_path);
|
||||
GetValue(bladedisc, BLADEDISC, model_path);
|
||||
GetValue(vad_dir, VAD_DIR, model_path);
|
||||
GetValue(vad_quant, VAD_QUANT, model_path);
|
||||
GetValue(punc_dir, PUNC_DIR, model_path);
|
||||
GetValue(punc_quant, PUNC_QUANT, model_path);
|
||||
GetValue(itn_dir, ITN_DIR, model_path);
|
||||
GetValue(lm_dir, LM_DIR, model_path);
|
||||
GetValue(hotword, HOTWORD, model_path);
|
||||
|
||||
GetValue(model_revision, "model-revision", model_path);
|
||||
GetValue(vad_revision, "vad-revision", model_path);
|
||||
GetValue(punc_revision, "punc-revision", model_path);
|
||||
GetValue(itn_revision, "itn-revision", model_path);
|
||||
GetValue(lm_revision, "lm-revision", model_path);
|
||||
|
||||
global_beam_ = global_beam.getValue();
|
||||
lattice_beam_ = lattice_beam.getValue();
|
||||
am_scale_ = am_scale.getValue();
|
||||
bool use_gpu_ = use_gpu.getValue();
|
||||
int batch_size_ = batch_size.getValue();
|
||||
|
||||
// Download model form Modelscope
|
||||
try{
|
||||
std::string s_download_model_dir = download_model_dir.getValue();
|
||||
|
||||
std::string s_vad_path = model_path[VAD_DIR];
|
||||
std::string s_vad_quant = model_path[VAD_QUANT];
|
||||
std::string s_asr_path = model_path[MODEL_DIR];
|
||||
std::string s_asr_quant = model_path[QUANTIZE];
|
||||
std::string s_punc_path = model_path[PUNC_DIR];
|
||||
std::string s_punc_quant = model_path[PUNC_QUANT];
|
||||
std::string s_itn_path = model_path[ITN_DIR];
|
||||
std::string s_lm_path = model_path[LM_DIR];
|
||||
std::string s_blade = model_path[BLADEDISC];
|
||||
|
||||
std::string python_cmd = "python -m funasr.download.runtime_sdk_download_tool ";
|
||||
|
||||
if(vad_dir.isSet() && !s_vad_path.empty()){
|
||||
std::string python_cmd_vad;
|
||||
std::string down_vad_path;
|
||||
std::string down_vad_model;
|
||||
|
||||
if (access(s_vad_path.c_str(), F_OK) == 0){
|
||||
// local
|
||||
python_cmd_vad = python_cmd + " --type onnx " + " --quantize " + s_vad_quant + " --model-name " + s_vad_path + " --export-dir ./ " + " --model_revision " + model_path["vad-revision"];
|
||||
down_vad_path = s_vad_path;
|
||||
}else{
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_vad_path << " from modelscope: ";
|
||||
python_cmd_vad = python_cmd + " --type onnx " + " --quantize " + s_vad_quant + " --model-name " + s_vad_path + " --export-dir " + s_download_model_dir + " --model_revision " + model_path["vad-revision"];
|
||||
down_vad_path = s_download_model_dir+"/"+s_vad_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_vad.c_str());
|
||||
if(ret !=0){
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local vad model path, you can ignore the errors.";
|
||||
}
|
||||
down_vad_model = down_vad_path+"/model_quant.onnx";
|
||||
if(s_vad_quant=="false" || s_vad_quant=="False" || s_vad_quant=="FALSE"){
|
||||
down_vad_model = down_vad_path+"/model.onnx";
|
||||
}
|
||||
|
||||
if (access(down_vad_model.c_str(), F_OK) != 0){
|
||||
LOG(ERROR) << down_vad_model << " do not exists.";
|
||||
exit(-1);
|
||||
}else{
|
||||
model_path[VAD_DIR]=down_vad_path;
|
||||
LOG(INFO) << "Set " << VAD_DIR << " : " << model_path[VAD_DIR];
|
||||
}
|
||||
}else{
|
||||
LOG(INFO) << "VAD model is not set, use default.";
|
||||
}
|
||||
|
||||
if(model_dir.isSet() && !s_asr_path.empty()){
|
||||
std::string python_cmd_asr;
|
||||
std::string down_asr_path;
|
||||
std::string down_asr_model;
|
||||
std::string model_type = "onnx";
|
||||
|
||||
// modify model-revision by model name
|
||||
size_t found = s_asr_path.find("speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404");
|
||||
if (found != std::string::npos) {
|
||||
model_path["model-revision"]="v2.0.5";
|
||||
}
|
||||
|
||||
found = s_asr_path.find("speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404");
|
||||
if (found != std::string::npos) {
|
||||
model_path["model-revision"]="v2.0.5";
|
||||
}
|
||||
|
||||
found = s_asr_path.find("speech_paraformer-large_asr_nat-en-16k-common-vocab10020");
|
||||
if (found != std::string::npos) {
|
||||
model_path["model-revision"]="v2.0.5";
|
||||
s_itn_path="";
|
||||
s_lm_path="";
|
||||
}
|
||||
|
||||
found = s_asr_path.find(MODEL_SVS);
|
||||
if (found != std::string::npos) {
|
||||
model_path["model-revision"]="v2.0.5";
|
||||
s_itn_path="";
|
||||
model_path[ITN_DIR]="";
|
||||
s_lm_path="";
|
||||
model_path[LM_DIR]="";
|
||||
s_punc_path="";
|
||||
model_path[PUNC_DIR]="";
|
||||
}
|
||||
|
||||
if (use_gpu_){
|
||||
model_type = "torchscript";
|
||||
if (s_blade=="true" || s_blade=="True" || s_blade=="TRUE"){
|
||||
model_type = "bladedisc";
|
||||
}
|
||||
}
|
||||
|
||||
if (access(s_asr_path.c_str(), F_OK) == 0){
|
||||
// local
|
||||
python_cmd_asr = python_cmd + " --type " + model_type + " --quantize " + s_asr_quant + " --model-name " + s_asr_path + " --export-dir ./ " + " --model_revision " + model_path["model-revision"];
|
||||
down_asr_path = s_asr_path;
|
||||
}else{
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_asr_path << " from modelscope: ";
|
||||
python_cmd_asr = python_cmd + " --type " + model_type + " --quantize " + s_asr_quant + " --model-name " + s_asr_path + " --export-dir " + s_download_model_dir + " --model_revision " + model_path["model-revision"];
|
||||
down_asr_path = s_download_model_dir+"/"+s_asr_path;
|
||||
}
|
||||
|
||||
down_asr_model = down_asr_path+"/model_quant.onnx";
|
||||
if(s_asr_quant=="false" || s_asr_quant=="False" || s_asr_quant=="FALSE"){
|
||||
down_asr_model = down_asr_path+"/model.onnx";
|
||||
}
|
||||
|
||||
if (use_gpu_){
|
||||
down_asr_model = down_asr_path+"/model.torchscript";
|
||||
if (s_blade=="true" || s_blade=="True" || s_blade=="TRUE"){
|
||||
down_asr_model = down_asr_path+"/model_blade.torchscript";
|
||||
}
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_asr.c_str());
|
||||
if(ret !=0){
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local asr model path, you can ignore the errors.";
|
||||
}
|
||||
|
||||
if (access(down_asr_model.c_str(), F_OK) != 0){
|
||||
LOG(ERROR) << down_asr_model << " do not exists.";
|
||||
exit(-1);
|
||||
}else{
|
||||
model_path[MODEL_DIR]=down_asr_path;
|
||||
LOG(INFO) << "Set " << MODEL_DIR << " : " << model_path[MODEL_DIR];
|
||||
}
|
||||
}else{
|
||||
LOG(INFO) << "ASR model is not set, use default.";
|
||||
}
|
||||
|
||||
if (!s_itn_path.empty()) {
|
||||
std::string python_cmd_itn;
|
||||
std::string down_itn_path;
|
||||
std::string down_itn_model;
|
||||
|
||||
if (access(s_itn_path.c_str(), F_OK) == 0) {
|
||||
// local
|
||||
python_cmd_itn = python_cmd + " --type onnx " + " --model-name " + s_itn_path +
|
||||
" --export-dir ./ " + " --model_revision " +
|
||||
model_path["itn-revision"] + " --export False ";
|
||||
down_itn_path = s_itn_path;
|
||||
} else {
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_itn_path
|
||||
<< " from modelscope : ";
|
||||
python_cmd_itn = python_cmd + " --type onnx " + " --model-name " +
|
||||
s_itn_path +
|
||||
" --export-dir " + s_download_model_dir +
|
||||
" --model_revision " + model_path["itn-revision"]
|
||||
+ " --export False ";
|
||||
down_itn_path =
|
||||
s_download_model_dir +
|
||||
"/" + s_itn_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_itn.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local itn model path, you can ignore the errors.";
|
||||
}
|
||||
down_itn_model = down_itn_path + "/zh_itn_tagger.fst";
|
||||
|
||||
if (access(down_itn_model.c_str(), F_OK) != 0) {
|
||||
LOG(ERROR) << down_itn_model << " do not exists.";
|
||||
exit(-1);
|
||||
} else {
|
||||
model_path[ITN_DIR] = down_itn_path;
|
||||
LOG(INFO) << "Set " << ITN_DIR << " : " << model_path[ITN_DIR];
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "ITN model is not set, not executed.";
|
||||
}
|
||||
|
||||
if (!s_lm_path.empty() && s_lm_path != "NONE" && s_lm_path != "none") {
|
||||
std::string python_cmd_lm;
|
||||
std::string down_lm_path;
|
||||
std::string down_lm_model;
|
||||
|
||||
if (access(s_lm_path.c_str(), F_OK) == 0) {
|
||||
// local
|
||||
python_cmd_lm = python_cmd + " --type onnx " + " --model-name " + s_lm_path +
|
||||
" --export-dir ./ " + " --model_revision " +
|
||||
model_path["lm-revision"] + " --export False ";
|
||||
down_lm_path = s_lm_path;
|
||||
} else {
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_lm_path
|
||||
<< " from modelscope : ";
|
||||
python_cmd_lm = python_cmd + " --type onnx " + " --model-name " +
|
||||
s_lm_path +
|
||||
" --export-dir " + s_download_model_dir +
|
||||
" --model_revision " + model_path["lm-revision"]
|
||||
+ " --export False ";
|
||||
down_lm_path =
|
||||
s_download_model_dir +
|
||||
"/" + s_lm_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_lm.c_str());
|
||||
if (ret != 0) {
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local lm model path, you can ignore the errors.";
|
||||
}
|
||||
down_lm_model = down_lm_path + "/TLG.fst";
|
||||
|
||||
if (access(down_lm_model.c_str(), F_OK) != 0) {
|
||||
LOG(ERROR) << down_lm_model << " do not exists.";
|
||||
exit(-1);
|
||||
} else {
|
||||
model_path[LM_DIR] = down_lm_path;
|
||||
LOG(INFO) << "Set " << LM_DIR << " : " << model_path[LM_DIR];
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "LM model is not set, not executed.";
|
||||
model_path[LM_DIR] = "";
|
||||
}
|
||||
|
||||
if(punc_dir.isSet() && !s_punc_path.empty()){
|
||||
std::string python_cmd_punc;
|
||||
std::string down_punc_path;
|
||||
std::string down_punc_model;
|
||||
|
||||
if (access(s_punc_path.c_str(), F_OK) == 0){
|
||||
// local
|
||||
python_cmd_punc = python_cmd + " --type onnx " + "--quantize " + s_punc_quant + " --model-name " + s_punc_path + " --export-dir ./ " + " --model_revision " + model_path["punc-revision"];
|
||||
down_punc_path = s_punc_path;
|
||||
}else{
|
||||
// modelscope
|
||||
LOG(INFO) << "Download model: " << s_punc_path << " from modelscope: ";
|
||||
python_cmd_punc = python_cmd + " --type onnx " + "--quantize " + s_punc_quant + " --model-name " + s_punc_path + " --export-dir " + s_download_model_dir + " --model_revision " + model_path["punc-revision"];
|
||||
down_punc_path = s_download_model_dir+"/"+s_punc_path;
|
||||
}
|
||||
|
||||
int ret = system(python_cmd_punc.c_str());
|
||||
if(ret !=0){
|
||||
LOG(INFO) << "Failed to download model from modelscope. If you set local punc model path, you can ignore the errors.";
|
||||
}
|
||||
down_punc_model = down_punc_path+"/model_quant.onnx";
|
||||
if(s_punc_quant=="false" || s_punc_quant=="False" || s_punc_quant=="FALSE"){
|
||||
down_punc_model = down_punc_path+"/model.onnx";
|
||||
}
|
||||
|
||||
if (access(down_punc_model.c_str(), F_OK) != 0){
|
||||
LOG(ERROR) << down_punc_model << " do not exists.";
|
||||
exit(-1);
|
||||
}else{
|
||||
model_path[PUNC_DIR]=down_punc_path;
|
||||
LOG(INFO) << "Set " << PUNC_DIR << " : " << model_path[PUNC_DIR];
|
||||
}
|
||||
}else{
|
||||
LOG(INFO) << "PUNC model is not set, use default.";
|
||||
}
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
LOG(ERROR) << "Error: " << e.what();
|
||||
}
|
||||
|
||||
std::string s_listen_ip = listen_ip.getValue();
|
||||
int s_port = port.getValue();
|
||||
int s_io_thread_num = io_thread_num.getValue();
|
||||
int s_decoder_thread_num = decoder_thread_num.getValue();
|
||||
|
||||
int s_model_thread_num = model_thread_num.getValue();
|
||||
|
||||
asio::io_context io_decoder; // context for decoding
|
||||
asio::io_context io_server; // context for server
|
||||
|
||||
std::vector<std::thread> decoder_threads;
|
||||
|
||||
std::string s_certfile = certfile.getValue();
|
||||
std::string s_keyfile = keyfile.getValue();
|
||||
|
||||
// hotword file
|
||||
std::string hotword_path;
|
||||
hotword_path = model_path.at(HOTWORD);
|
||||
fst_inc_wts_ = fst_inc_wts.getValue();
|
||||
LOG(INFO) << "hotword path: " << hotword_path;
|
||||
funasr::ExtractHws(hotword_path, hws_map_);
|
||||
|
||||
bool is_ssl = false;
|
||||
if (!s_certfile.empty() && access(s_certfile.c_str(), F_OK) == 0) {
|
||||
is_ssl = true;
|
||||
}
|
||||
|
||||
auto conn_guard = asio::make_work_guard(
|
||||
io_decoder); // make sure threads can wait in the queue
|
||||
auto server_guard = asio::make_work_guard(
|
||||
io_server); // make sure threads can wait in the queue
|
||||
// create threads pool
|
||||
for (int32_t i = 0; i < s_decoder_thread_num; ++i) {
|
||||
decoder_threads.emplace_back([&io_decoder]() { io_decoder.run(); });
|
||||
}
|
||||
|
||||
server server_; // server for websocket
|
||||
wss_server wss_server_;
|
||||
server* server = nullptr;
|
||||
wss_server* wss_server = nullptr;
|
||||
if (is_ssl) {
|
||||
LOG(INFO)<< "SSL is opened!";
|
||||
wss_server_.init_asio(&io_server); // init asio
|
||||
wss_server_.set_reuse_addr(
|
||||
true); // reuse address as we create multiple threads
|
||||
|
||||
// list on port for accept
|
||||
wss_server_.listen(asio::ip::address::from_string(s_listen_ip), s_port);
|
||||
wss_server = &wss_server_;
|
||||
} else {
|
||||
LOG(INFO)<< "SSL is closed!";
|
||||
server_.init_asio(&io_server); // init asio
|
||||
server_.set_reuse_addr(
|
||||
true); // reuse address as we create multiple threads
|
||||
|
||||
// list on port for accept
|
||||
server_.listen(asio::ip::address::from_string(s_listen_ip), s_port);
|
||||
server = &server_;
|
||||
}
|
||||
|
||||
|
||||
WebSocketServer websocket_srv(
|
||||
io_decoder, is_ssl, server, wss_server, s_certfile,
|
||||
s_keyfile); // websocket server for asr engine
|
||||
websocket_srv.initAsr(model_path, s_model_thread_num, use_gpu_, batch_size_); // init asr model
|
||||
|
||||
LOG(INFO) << "decoder-thread-num: " << s_decoder_thread_num;
|
||||
LOG(INFO) << "io-thread-num: " << s_io_thread_num;
|
||||
LOG(INFO) << "model-thread-num: " << s_model_thread_num;
|
||||
LOG(INFO) << "asr model init finished. listen on port:" << s_port;
|
||||
|
||||
// Start the ASIO network io_service run loop
|
||||
std::vector<std::thread> ts;
|
||||
// create threads for io network
|
||||
for (size_t i = 0; i < s_io_thread_num; i++) {
|
||||
ts.emplace_back([&io_server]() { io_server.run(); });
|
||||
}
|
||||
// wait for theads
|
||||
for (size_t i = 0; i < s_io_thread_num; i++) {
|
||||
ts[i].join();
|
||||
}
|
||||
|
||||
// wait for theads
|
||||
for (auto& t : decoder_threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
LOG(ERROR) << "Error: " << e.what();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include "microphone.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "portaudio.h" // NOLINT
|
||||
|
||||
Microphone::Microphone() {
|
||||
PaError err = Pa_Initialize();
|
||||
if (err != paNoError) {
|
||||
LOG(ERROR)<<"portaudio error: " << Pa_GetErrorText(err);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
Microphone::~Microphone() {
|
||||
PaError err = Pa_Terminate();
|
||||
if (err != paNoError) {
|
||||
LOG(ERROR)<<"portaudio error: " << Pa_GetErrorText(err);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#ifndef WEBSOCKET_MICROPHONE_H_
|
||||
#define WEBSOCKET_MICROPHONE_H_
|
||||
#include <glog/logging.h>
|
||||
|
||||
class Microphone {
|
||||
public:
|
||||
Microphone();
|
||||
~Microphone();
|
||||
};
|
||||
|
||||
#endif // WEBSOCKET_MICROPHONE_H_
|
||||
@@ -0,0 +1,649 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaomingwork */
|
||||
|
||||
// websocket server for asr engine
|
||||
// take some ideas from https://github.com/k2-fsa/sherpa-onnx
|
||||
// online-websocket-server-impl.cc, thanks. The websocket server has two threads
|
||||
// pools, one for handle network data and one for asr decoder.
|
||||
// now only support offline engine.
|
||||
|
||||
#include "websocket-server-2pass.h"
|
||||
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
extern std::unordered_map<std::string, int> hws_map_;
|
||||
extern int fst_inc_wts_;
|
||||
extern float global_beam_, lattice_beam_, am_scale_;
|
||||
|
||||
int64_t getCurrentTimeMillis() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||
return millis;
|
||||
}
|
||||
|
||||
context_ptr WebSocketServer::on_tls_init(tls_mode mode,
|
||||
websocketpp::connection_hdl hdl,
|
||||
std::string& s_certfile,
|
||||
std::string& s_keyfile) {
|
||||
namespace asio = websocketpp::lib::asio;
|
||||
|
||||
LOG(INFO) << "on_tls_init called with hdl: " << hdl.lock().get();
|
||||
LOG(INFO) << "using TLS mode: "
|
||||
<< (mode == MOZILLA_MODERN ? "Mozilla Modern"
|
||||
: "Mozilla Intermediate");
|
||||
|
||||
context_ptr ctx = websocketpp::lib::make_shared<asio::ssl::context>(
|
||||
asio::ssl::context::sslv23);
|
||||
|
||||
try {
|
||||
if (mode == MOZILLA_MODERN) {
|
||||
// Modern disables TLSv1
|
||||
ctx->set_options(
|
||||
asio::ssl::context::default_workarounds |
|
||||
asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3 |
|
||||
asio::ssl::context::no_tlsv1 | asio::ssl::context::single_dh_use);
|
||||
} else {
|
||||
ctx->set_options(asio::ssl::context::default_workarounds |
|
||||
asio::ssl::context::no_sslv2 |
|
||||
asio::ssl::context::no_sslv3 |
|
||||
asio::ssl::context::single_dh_use);
|
||||
}
|
||||
|
||||
ctx->use_certificate_chain_file(s_certfile);
|
||||
ctx->use_private_key_file(s_keyfile, asio::ssl::context::pem);
|
||||
|
||||
} catch (std::exception& e) {
|
||||
LOG(INFO) << "Exception: " << e.what();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
nlohmann::json handle_result(FUNASR_RESULT result, websocketpp::connection_hdl& hdl, std::map<websocketpp::connection_hdl, std::shared_ptr<FUNASR_MESSAGE>,std::owner_less<websocketpp::connection_hdl>>& data_map) {
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg = nullptr;
|
||||
auto it = data_map.find(hdl);
|
||||
if (it != data_map.end()) {
|
||||
data_msg = it->second;
|
||||
}
|
||||
|
||||
websocketpp::lib::error_code ec;
|
||||
nlohmann::json jsonresult;
|
||||
jsonresult["text"] = "";
|
||||
|
||||
std::string tmp_online_msg = FunASRGetResult(result, 0);
|
||||
if (tmp_online_msg != "") {
|
||||
LOG(INFO) << "wav_name: " << data_msg->msg["wav_name"].get<std::string>() << " | online_res :" << tmp_online_msg;
|
||||
jsonresult["text"] = tmp_online_msg;
|
||||
jsonresult["mode"] = "2pass-online";
|
||||
jsonresult["slice_type"] = 1;
|
||||
jsonresult["index"] = data_msg->index;
|
||||
|
||||
// 如果是第一句话的第一个实时结果或新的句子开始
|
||||
if (!data_msg->is_sentence_started) {
|
||||
data_msg->start_time = FunASRGetTpassStart(result); // 记录句子的开始时间
|
||||
jsonresult["slice_type"] = 0; //0:一段话开始识别; 1:一段话识别中; 2:一段话识别结束
|
||||
data_msg->is_sentence_started = true;
|
||||
}
|
||||
}
|
||||
|
||||
data_msg->end_time = FunASRGetTpassEnd(result); // 记录句子的结束时间
|
||||
jsonresult["timestamp"] = data_msg->timestamp;
|
||||
|
||||
std::string tmp_tpass_msg = FunASRGetTpassResult(result, 0);
|
||||
if (tmp_tpass_msg != "") {
|
||||
LOG(INFO) << "wav_name: " << data_msg->msg["wav_name"].get<std::string>() << " | offline results : " << tmp_tpass_msg;
|
||||
jsonresult["text"] = tmp_tpass_msg;
|
||||
jsonresult["mode"] = "2pass-offline";
|
||||
|
||||
// 句子结束,记录结束时间
|
||||
jsonresult["start_time"] = data_msg->start_time;
|
||||
jsonresult["end_time"] = data_msg->end_time;
|
||||
jsonresult["slice_type"] = 2;
|
||||
jsonresult["index"] = data_msg->index;
|
||||
|
||||
data_msg->index++; //句子序号
|
||||
data_msg->is_sentence_started = false; // 重置句子状态
|
||||
}
|
||||
|
||||
std::string tmp_stamp_msg = FunASRGetStamp(result);
|
||||
if (tmp_stamp_msg != "") {
|
||||
LOG(INFO) << "offline stamps : " << tmp_stamp_msg;
|
||||
jsonresult["timestamp"] = tmp_stamp_msg;
|
||||
}
|
||||
|
||||
std::string tmp_stamp_sents = FunASRGetStampSents(result);
|
||||
if (tmp_stamp_sents != "") {
|
||||
try{
|
||||
nlohmann::json json_stamp = nlohmann::json::parse(tmp_stamp_sents);
|
||||
LOG(INFO) << "offline stamp_sents : " << json_stamp;
|
||||
jsonresult["stamp_sents"] = json_stamp;
|
||||
}catch (std::exception const &e)
|
||||
{
|
||||
LOG(ERROR)<< tmp_stamp_sents << e.what();
|
||||
jsonresult["stamp_sents"] = "";
|
||||
}
|
||||
}
|
||||
|
||||
return jsonresult;
|
||||
}
|
||||
// feed buffer to asr engine for decoder
|
||||
void WebSocketServer::do_decoder(
|
||||
std::map<websocketpp::connection_hdl, std::shared_ptr<FUNASR_MESSAGE>,std::owner_less<websocketpp::connection_hdl>>& data_map,
|
||||
std::vector<char>& buffer,
|
||||
websocketpp::connection_hdl& hdl,
|
||||
nlohmann::json& msg,
|
||||
std::vector<std::vector<std::string>>& punc_cache,
|
||||
std::vector<std::vector<float>> &hotwords_embedding,
|
||||
websocketpp::lib::mutex& thread_lock,
|
||||
bool& is_final,
|
||||
std::string wav_name,
|
||||
std::string modetype,
|
||||
bool itn,
|
||||
int audio_fs,
|
||||
std::string wav_format,
|
||||
FUNASR_HANDLE& tpass_online_handle,
|
||||
FUNASR_DEC_HANDLE& decoder_handle,
|
||||
std::string svs_lang,
|
||||
bool sys_itn) {
|
||||
// lock for each connection
|
||||
if(!tpass_online_handle){
|
||||
scoped_lock guard(thread_lock);
|
||||
LOG(INFO) << "tpass_online_handle is free, return";
|
||||
msg["access_num"]=(int)msg["access_num"]-1;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FUNASR_RESULT Result = nullptr;
|
||||
int asr_mode_ = 2;
|
||||
if (modetype == "offline") {
|
||||
asr_mode_ = 0;
|
||||
} else if (modetype == "online") {
|
||||
asr_mode_ = 1;
|
||||
} else if (modetype == "2pass") {
|
||||
asr_mode_ = 2;
|
||||
}
|
||||
|
||||
while (buffer.size() >= 800 * 2 && !msg["is_eof"]) {
|
||||
std::vector<char> subvector = {buffer.begin(), buffer.begin() + 800 * 2};
|
||||
buffer.erase(buffer.begin(), buffer.begin() + 800 * 2);
|
||||
|
||||
try {
|
||||
if (tpass_online_handle) {
|
||||
Result = FunTpassInferBuffer(tpass_handle, tpass_online_handle,
|
||||
subvector.data(), subvector.size(),
|
||||
punc_cache, false, audio_fs,
|
||||
wav_format, (ASR_TYPE)asr_mode_,
|
||||
hotwords_embedding, itn, decoder_handle,
|
||||
svs_lang, sys_itn);
|
||||
|
||||
} else {
|
||||
scoped_lock guard(thread_lock);
|
||||
msg["access_num"]=(int)msg["access_num"]-1;
|
||||
return;
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
scoped_lock guard(thread_lock);
|
||||
LOG(ERROR) << e.what();
|
||||
msg["access_num"]=(int)msg["access_num"]-1;
|
||||
return;
|
||||
}
|
||||
if (Result) {
|
||||
websocketpp::lib::error_code ec;
|
||||
nlohmann::json jsonresult = handle_result(Result, hdl, data_map);
|
||||
jsonresult["wav_name"] = wav_name;
|
||||
jsonresult["is_final"] = false;
|
||||
if (jsonresult["text"] != "") {
|
||||
//LOG(INFO) << "jsonresult: " << jsonresult.dump(4);
|
||||
if (is_ssl) {
|
||||
wss_server_->send(hdl, jsonresult.dump(),
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
} else {
|
||||
server_->send(hdl, jsonresult.dump(),
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
}
|
||||
}
|
||||
FunASRFreeResult(Result);
|
||||
}
|
||||
}
|
||||
if (is_final && !msg["is_eof"]) {
|
||||
try {
|
||||
if (tpass_online_handle) {
|
||||
Result = FunTpassInferBuffer(tpass_handle, tpass_online_handle,
|
||||
buffer.data(), buffer.size(), punc_cache,
|
||||
is_final, audio_fs,
|
||||
wav_format, (ASR_TYPE)asr_mode_,
|
||||
hotwords_embedding, itn, decoder_handle,
|
||||
svs_lang, sys_itn);
|
||||
} else {
|
||||
scoped_lock guard(thread_lock);
|
||||
msg["access_num"]=(int)msg["access_num"]-1;
|
||||
return;
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
scoped_lock guard(thread_lock);
|
||||
LOG(ERROR) << e.what();
|
||||
msg["access_num"]=(int)msg["access_num"]-1;
|
||||
return;
|
||||
}
|
||||
if(punc_cache.size()>0){
|
||||
for (auto& vec : punc_cache) {
|
||||
vec.clear();
|
||||
}
|
||||
}
|
||||
if (Result) {
|
||||
websocketpp::lib::error_code ec;
|
||||
nlohmann::json jsonresult = handle_result(Result, hdl, data_map);
|
||||
jsonresult["wav_name"] = wav_name;
|
||||
jsonresult["is_final"] = true;
|
||||
//LOG(INFO) << "jsonresult: " << jsonresult.dump(4);
|
||||
if (is_ssl) {
|
||||
wss_server_->send(hdl, jsonresult.dump(),
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
} else {
|
||||
server_->send(hdl, jsonresult.dump(),
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
}
|
||||
FunASRFreeResult(Result);
|
||||
}else{
|
||||
if(wav_format != "pcm" && wav_format != "PCM"){
|
||||
websocketpp::lib::error_code ec;
|
||||
nlohmann::json jsonresult;
|
||||
jsonresult["text"] = "ERROR. Real-time transcription service ONLY SUPPORT PCM stream.";
|
||||
jsonresult["wav_name"] = wav_name;
|
||||
jsonresult["is_final"] = true;
|
||||
if (is_ssl) {
|
||||
wss_server_->send(hdl, jsonresult.dump(),
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
} else {
|
||||
server_->send(hdl, jsonresult.dump(),
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
scoped_lock guard(thread_lock);
|
||||
msg["access_num"]=(int)msg["access_num"]-1;
|
||||
|
||||
}
|
||||
|
||||
void WebSocketServer::on_open(websocketpp::connection_hdl hdl) {
|
||||
scoped_lock guard(m_lock); // for threads safty
|
||||
try{
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg =
|
||||
std::make_shared<FUNASR_MESSAGE>(); // put a new data vector for new
|
||||
// connection
|
||||
data_msg->samples = std::make_shared<std::vector<char>>();
|
||||
data_msg->thread_lock = std::make_shared<websocketpp::lib::mutex>();
|
||||
|
||||
data_msg->msg = nlohmann::json::parse("{}");
|
||||
data_msg->msg["wav_format"] = "pcm";
|
||||
data_msg->msg["wav_name"] = "wav-default-id";
|
||||
data_msg->msg["mode"] = "2pass";
|
||||
data_msg->msg["itn"] = true;
|
||||
data_msg->msg["audio_fs"] = 16000; // default is 16k
|
||||
data_msg->msg["access_num"] = 0; // the number of access for this object, when it is 0, we can free it saftly
|
||||
data_msg->msg["is_eof"]=false; // if this connection is closed
|
||||
data_msg->msg["svs_lang"]="auto";
|
||||
data_msg->msg["svs_itn"]=true;
|
||||
FUNASR_DEC_HANDLE decoder_handle =
|
||||
FunASRWfstDecoderInit(tpass_handle, ASR_TWO_PASS, global_beam_, lattice_beam_, am_scale_);
|
||||
data_msg->decoder_handle = decoder_handle;
|
||||
data_msg->punc_cache =
|
||||
std::make_shared<std::vector<std::vector<std::string>>>(2);
|
||||
data_msg->strand_ = std::make_shared<asio::io_context::strand>(io_decoder_);
|
||||
|
||||
data_msg->is_sentence_started = false;
|
||||
|
||||
data_msg->timestamp = getCurrentTimeMillis();
|
||||
|
||||
data_map.emplace(hdl, data_msg);
|
||||
}catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void remove_hdl(
|
||||
websocketpp::connection_hdl hdl,
|
||||
std::map<websocketpp::connection_hdl, std::shared_ptr<FUNASR_MESSAGE>,
|
||||
std::owner_less<websocketpp::connection_hdl>>& data_map) {
|
||||
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg = nullptr;
|
||||
auto it_data = data_map.find(hdl);
|
||||
if (it_data != data_map.end()) {
|
||||
data_msg = it_data->second;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// scoped_lock guard_decoder(*(data_msg->thread_lock)); //wait for do_decoder
|
||||
// finished and avoid access freed tpass_online_handle
|
||||
unique_lock guard_decoder(*(data_msg->thread_lock));
|
||||
if (data_msg->msg["access_num"]==0 && data_msg->msg["is_eof"]==true) {
|
||||
FunWfstDecoderUnloadHwsRes(data_msg->decoder_handle);
|
||||
FunASRWfstDecoderUninit(data_msg->decoder_handle);
|
||||
data_msg->decoder_handle = nullptr;
|
||||
FunTpassOnlineUninit(data_msg->tpass_online_handle);
|
||||
data_msg->tpass_online_handle = nullptr;
|
||||
data_map.erase(hdl);
|
||||
}
|
||||
|
||||
guard_decoder.unlock();
|
||||
}
|
||||
|
||||
void WebSocketServer::on_close(websocketpp::connection_hdl hdl) {
|
||||
scoped_lock guard(m_lock);
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg = nullptr;
|
||||
auto it_data = data_map.find(hdl);
|
||||
if (it_data != data_map.end()) {
|
||||
data_msg = it_data->second;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
unique_lock guard_decoder(*(data_msg->thread_lock));
|
||||
data_msg->msg["is_eof"]=true;
|
||||
guard_decoder.unlock();
|
||||
}
|
||||
|
||||
// remove closed connection
|
||||
void WebSocketServer::check_and_clean_connection() {
|
||||
while(true){
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
std::vector<websocketpp::connection_hdl> to_remove; // remove list
|
||||
auto iter = data_map.begin();
|
||||
while (iter != data_map.end()) { // loop to find closed connection
|
||||
websocketpp::connection_hdl hdl = iter->first;
|
||||
try{
|
||||
if (is_ssl) {
|
||||
wss_server::connection_ptr con = wss_server_->get_con_from_hdl(hdl);
|
||||
if (con->get_state() != 1) { // session::state::open ==1
|
||||
to_remove.push_back(hdl);
|
||||
}
|
||||
} else {
|
||||
server::connection_ptr con = server_->get_con_from_hdl(hdl);
|
||||
if (con->get_state() != 1) { // session::state::open ==1
|
||||
to_remove.push_back(hdl);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
// if connection is close, we set is_eof = true
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg = nullptr;
|
||||
auto it_data = data_map.find(hdl);
|
||||
if (it_data != data_map.end()) {
|
||||
data_msg = it_data->second;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
unique_lock guard_decoder(*(data_msg->thread_lock));
|
||||
data_msg->msg["is_eof"]=true;
|
||||
guard_decoder.unlock();
|
||||
to_remove.push_back(hdl);
|
||||
LOG(INFO)<<"connection is closed.";
|
||||
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
for (auto hdl : to_remove) {
|
||||
{
|
||||
unique_lock lock(m_lock);
|
||||
remove_hdl(hdl, data_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void WebSocketServer::on_message(websocketpp::connection_hdl hdl,
|
||||
message_ptr msg) {
|
||||
unique_lock lock(m_lock);
|
||||
// find the sample data vector according to one connection
|
||||
|
||||
std::shared_ptr<FUNASR_MESSAGE> msg_data = nullptr;
|
||||
|
||||
auto it_data = data_map.find(hdl);
|
||||
if (it_data != data_map.end()) {
|
||||
msg_data = it_data->second;
|
||||
if(msg_data->msg["is_eof"]){
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<char>> sample_data_p = msg_data->samples;
|
||||
std::shared_ptr<std::vector<std::vector<std::string>>> punc_cache_p =
|
||||
msg_data->punc_cache;
|
||||
std::shared_ptr<websocketpp::lib::mutex> thread_lock_p = msg_data->thread_lock;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (sample_data_p == nullptr) {
|
||||
LOG(INFO) << "error when fetch sample data vector";
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& payload = msg->get_payload(); // get msg type
|
||||
unique_lock guard_decoder(*(thread_lock_p)); // mutex for one connection
|
||||
switch (msg->get_opcode()) {
|
||||
case websocketpp::frame::opcode::text: {
|
||||
nlohmann::json jsonresult;
|
||||
try{
|
||||
jsonresult = nlohmann::json::parse(payload);
|
||||
}catch (std::exception const &e)
|
||||
{
|
||||
LOG(ERROR)<<e.what();
|
||||
msg_data->msg["is_eof"]=true;
|
||||
guard_decoder.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonresult.contains("wav_name")) {
|
||||
msg_data->msg["wav_name"] = jsonresult["wav_name"];
|
||||
}
|
||||
if (jsonresult.contains("mode")) {
|
||||
msg_data->msg["mode"] = jsonresult["mode"];
|
||||
}
|
||||
if (jsonresult.contains("wav_format")) {
|
||||
msg_data->msg["wav_format"] = jsonresult["wav_format"];
|
||||
}
|
||||
|
||||
// hotwords: fst/nn
|
||||
if(msg_data->hotwords_embedding == nullptr){
|
||||
std::unordered_map<std::string, int> merged_hws_map;
|
||||
std::string nn_hotwords = "";
|
||||
|
||||
if (jsonresult["hotwords"] != nullptr) {
|
||||
std::string json_string = jsonresult["hotwords"];
|
||||
if (!json_string.empty()){
|
||||
nlohmann::json json_fst_hws;
|
||||
try{
|
||||
json_fst_hws = nlohmann::json::parse(json_string);
|
||||
if(json_fst_hws.type() == nlohmann::json::value_t::object){
|
||||
// fst
|
||||
try{
|
||||
std::unordered_map<std::string, int> client_hws_map = json_fst_hws;
|
||||
merged_hws_map.insert(client_hws_map.begin(), client_hws_map.end());
|
||||
} catch (const std::exception& e) {
|
||||
LOG(INFO) << e.what();
|
||||
}
|
||||
}
|
||||
} catch (std::exception const &e)
|
||||
{
|
||||
LOG(ERROR)<<e.what();
|
||||
// nn
|
||||
std::string client_nn_hws = jsonresult["hotwords"];
|
||||
nn_hotwords += " " + client_nn_hws;
|
||||
// LOG(INFO) << "nn hotwords: " << client_nn_hws;
|
||||
}
|
||||
}
|
||||
}
|
||||
merged_hws_map.insert(hws_map_.begin(), hws_map_.end());
|
||||
|
||||
// fst
|
||||
LOG(INFO) << "hotwords: ";
|
||||
for (const auto& pair : merged_hws_map) {
|
||||
nn_hotwords += " " + pair.first;
|
||||
LOG(INFO) << pair.first << " : " << pair.second;
|
||||
}
|
||||
FunWfstDecoderLoadHwsRes(msg_data->decoder_handle, fst_inc_wts_, merged_hws_map);
|
||||
|
||||
// nn
|
||||
std::vector<std::vector<float>> new_hotwords_embedding = CompileHotwordEmbedding(tpass_handle, nn_hotwords, ASR_TWO_PASS);
|
||||
msg_data->hotwords_embedding =
|
||||
std::make_shared<std::vector<std::vector<float>>>(new_hotwords_embedding);
|
||||
}
|
||||
|
||||
if (jsonresult.contains("audio_fs")) {
|
||||
msg_data->msg["audio_fs"] = jsonresult["audio_fs"];
|
||||
}
|
||||
if (jsonresult.contains("chunk_size")) {
|
||||
if (msg_data->tpass_online_handle == nullptr) {
|
||||
std::vector<int> chunk_size_vec =
|
||||
jsonresult["chunk_size"].get<std::vector<int>>();
|
||||
// check chunk_size_vec
|
||||
if(chunk_size_vec.size() == 3 && chunk_size_vec[1] != 0){
|
||||
FUNASR_HANDLE tpass_online_handle =
|
||||
FunTpassOnlineInit(tpass_handle, chunk_size_vec);
|
||||
msg_data->tpass_online_handle = tpass_online_handle;
|
||||
}else{
|
||||
LOG(ERROR) << "Wrong chunk_size!";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jsonresult.contains("itn")) {
|
||||
msg_data->msg["itn"] = jsonresult["itn"];
|
||||
}
|
||||
if (jsonresult.contains("svs_lang")) {
|
||||
msg_data->msg["svs_lang"] = jsonresult["svs_lang"];
|
||||
}
|
||||
if (jsonresult.contains("svs_itn")) {
|
||||
msg_data->msg["svs_itn"] = jsonresult["svs_itn"];
|
||||
}
|
||||
LOG(INFO) << "jsonresult=" << jsonresult
|
||||
<< ", msg_data->msg=" << msg_data->msg;
|
||||
if ((jsonresult["is_speaking"] == false ||
|
||||
jsonresult["is_finished"] == true) &&
|
||||
msg_data->msg["is_eof"] != true &&
|
||||
msg_data->hotwords_embedding != nullptr) {
|
||||
LOG(INFO) << "client done";
|
||||
|
||||
// if it is in final message, post the sample_data to decode
|
||||
try{
|
||||
|
||||
std::vector<std::vector<float>> hotwords_embedding_(*(msg_data->hotwords_embedding));
|
||||
msg_data->strand_->post(
|
||||
std::bind(&WebSocketServer::do_decoder, this,
|
||||
data_map,
|
||||
std::move(*(sample_data_p.get())), std::move(hdl),
|
||||
std::ref(msg_data->msg), std::ref(*(punc_cache_p.get())),
|
||||
std::move(hotwords_embedding_),
|
||||
std::ref(*thread_lock_p), std::move(true),
|
||||
msg_data->msg["wav_name"],
|
||||
msg_data->msg["mode"],
|
||||
msg_data->msg["itn"],
|
||||
msg_data->msg["audio_fs"],
|
||||
msg_data->msg["wav_format"],
|
||||
std::ref(msg_data->tpass_online_handle),
|
||||
std::ref(msg_data->decoder_handle),
|
||||
msg_data->msg["svs_lang"],
|
||||
msg_data->msg["svs_itn"]));
|
||||
msg_data->msg["access_num"]=(int)(msg_data->msg["access_num"])+1;
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
LOG(ERROR)<<e.what();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case websocketpp::frame::opcode::binary: {
|
||||
// recived binary data
|
||||
const auto* pcm_data = static_cast<const char*>(payload.data());
|
||||
int32_t num_samples = payload.size();
|
||||
|
||||
if (isonline) {
|
||||
sample_data_p->insert(sample_data_p->end(), pcm_data,
|
||||
pcm_data + num_samples);
|
||||
int setpsize =
|
||||
800 * 2; // TODO, need get from client
|
||||
// if sample_data size > setpsize, we post data to decode
|
||||
if (sample_data_p->size() > setpsize) {
|
||||
int chunksize = floor(sample_data_p->size() / setpsize);
|
||||
// make sure the subvector size is an integer multiple of setpsize
|
||||
std::vector<char> subvector = {
|
||||
sample_data_p->begin(),
|
||||
sample_data_p->begin() + chunksize * setpsize};
|
||||
// keep remain in sample_data
|
||||
sample_data_p->erase(sample_data_p->begin(),
|
||||
sample_data_p->begin() + chunksize * setpsize);
|
||||
|
||||
try{
|
||||
// post to decode
|
||||
if (msg_data->msg["is_eof"] != true && msg_data->hotwords_embedding != nullptr) {
|
||||
std::vector<std::vector<float>> hotwords_embedding_(*(msg_data->hotwords_embedding));
|
||||
msg_data->strand_->post(
|
||||
std::bind(&WebSocketServer::do_decoder, this,
|
||||
data_map,
|
||||
std::move(subvector), std::move(hdl),
|
||||
std::ref(msg_data->msg),
|
||||
std::ref(*(punc_cache_p.get())),
|
||||
std::move(hotwords_embedding_),
|
||||
std::ref(*thread_lock_p), std::move(false),
|
||||
msg_data->msg["wav_name"],
|
||||
msg_data->msg["mode"],
|
||||
msg_data->msg["itn"],
|
||||
msg_data->msg["audio_fs"],
|
||||
msg_data->msg["wav_format"],
|
||||
std::ref(msg_data->tpass_online_handle),
|
||||
std::ref(msg_data->decoder_handle),
|
||||
msg_data->msg["svs_lang"],
|
||||
msg_data->msg["svs_itn"]));
|
||||
msg_data->msg["access_num"]=(int)(msg_data->msg["access_num"])+1;
|
||||
}
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
LOG(ERROR)<<e.what();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sample_data_p->insert(sample_data_p->end(), pcm_data,
|
||||
pcm_data + num_samples);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
guard_decoder.unlock();
|
||||
}
|
||||
|
||||
// init asr model
|
||||
void WebSocketServer::initAsr(std::map<std::string, std::string>& model_path,
|
||||
int thread_num) {
|
||||
try {
|
||||
tpass_handle = FunTpassInit(model_path, thread_num);
|
||||
if (!tpass_handle) {
|
||||
LOG(ERROR) << "FunTpassInit init failed";
|
||||
exit(-1);
|
||||
}
|
||||
LOG(INFO) << "initAsr run check_and_clean_connection";
|
||||
std::thread clean_thread(&WebSocketServer::check_and_clean_connection,this);
|
||||
clean_thread.detach();
|
||||
LOG(INFO) << "initAsr run check_and_clean_connection finished";
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
LOG(INFO) << e.what();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaomingwork */
|
||||
|
||||
// websocket server for asr engine
|
||||
// take some ideas from https://github.com/k2-fsa/sherpa-onnx
|
||||
// online-websocket-server-impl.cc, thanks. The websocket server has two threads
|
||||
// pools, one for handle network data and one for asr decoder.
|
||||
// now only support offline engine.
|
||||
|
||||
#ifndef WEBSOCKET_SERVER_H_
|
||||
#define WEBSOCKET_SERVER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#define ASIO_STANDALONE 1 // not boost
|
||||
#include <glog/logging.h>
|
||||
#include "util/text-utils.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <websocketpp/common/thread.hpp>
|
||||
#include <websocketpp/config/asio.hpp>
|
||||
#include <websocketpp/server.hpp>
|
||||
|
||||
#include "asio.hpp"
|
||||
#include "com-define.h"
|
||||
#include "funasrruntime.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "tclap/CmdLine.h"
|
||||
typedef websocketpp::server<websocketpp::config::asio> server;
|
||||
typedef websocketpp::server<websocketpp::config::asio_tls> wss_server;
|
||||
typedef server::message_ptr message_ptr;
|
||||
using websocketpp::lib::bind;
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::placeholders::_2;
|
||||
|
||||
typedef websocketpp::lib::lock_guard<websocketpp::lib::mutex> scoped_lock;
|
||||
typedef websocketpp::lib::unique_lock<websocketpp::lib::mutex> unique_lock;
|
||||
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>
|
||||
context_ptr;
|
||||
|
||||
typedef struct {
|
||||
std::string msg;
|
||||
float snippet_time;
|
||||
} FUNASR_RECOG_RESULT;
|
||||
|
||||
typedef struct {
|
||||
nlohmann::json msg;
|
||||
std::shared_ptr<std::vector<char>> samples;
|
||||
std::shared_ptr<std::vector<std::vector<std::string>>> punc_cache;
|
||||
std::shared_ptr<std::vector<std::vector<float>>> hotwords_embedding=nullptr;
|
||||
std::shared_ptr<websocketpp::lib::mutex> thread_lock; // lock for each connection
|
||||
FUNASR_HANDLE tpass_online_handle=nullptr;
|
||||
std::string online_res = "";
|
||||
std::string tpass_res = "";
|
||||
std::shared_ptr<asio::io_context::strand> strand_; // for data execute in order
|
||||
FUNASR_DEC_HANDLE decoder_handle=nullptr;
|
||||
|
||||
bool is_sentence_started = false;
|
||||
int64_t start_time = 0;
|
||||
int64_t end_time = 0;
|
||||
int64_t index = 0;
|
||||
int64_t timestamp = 0;
|
||||
} FUNASR_MESSAGE;
|
||||
|
||||
// See https://wiki.mozilla.org/Security/Server_Side_TLS for more details about
|
||||
// the TLS modes. The code below demonstrates how to implement both the modern
|
||||
enum tls_mode { MOZILLA_INTERMEDIATE = 1, MOZILLA_MODERN = 2 };
|
||||
class WebSocketServer {
|
||||
public:
|
||||
WebSocketServer(asio::io_context& io_decoder, bool is_ssl, server* server,
|
||||
wss_server* wss_server, std::string& s_certfile,
|
||||
std::string& s_keyfile)
|
||||
: io_decoder_(io_decoder),
|
||||
is_ssl(is_ssl),
|
||||
server_(server),
|
||||
wss_server_(wss_server) {
|
||||
if (is_ssl) {
|
||||
std::cout << "certfile path is " << s_certfile << std::endl;
|
||||
wss_server->set_tls_init_handler(
|
||||
bind<context_ptr>(&WebSocketServer::on_tls_init, this,
|
||||
MOZILLA_INTERMEDIATE, ::_1, s_certfile, s_keyfile));
|
||||
wss_server_->set_message_handler(
|
||||
[this](websocketpp::connection_hdl hdl, message_ptr msg) {
|
||||
on_message(hdl, msg);
|
||||
});
|
||||
// set open handle
|
||||
wss_server_->set_open_handler(
|
||||
[this](websocketpp::connection_hdl hdl) { on_open(hdl); });
|
||||
// set close handle
|
||||
wss_server_->set_close_handler(
|
||||
[this](websocketpp::connection_hdl hdl) { on_close(hdl); });
|
||||
// begin accept
|
||||
wss_server_->start_accept();
|
||||
// not print log
|
||||
wss_server_->clear_access_channels(websocketpp::log::alevel::all);
|
||||
|
||||
} else {
|
||||
// set message handle
|
||||
server_->set_message_handler(
|
||||
[this](websocketpp::connection_hdl hdl, message_ptr msg) {
|
||||
on_message(hdl, msg);
|
||||
});
|
||||
// set open handle
|
||||
server_->set_open_handler(
|
||||
[this](websocketpp::connection_hdl hdl) { on_open(hdl); });
|
||||
// set close handle
|
||||
server_->set_close_handler(
|
||||
[this](websocketpp::connection_hdl hdl) { on_close(hdl); });
|
||||
// begin accept
|
||||
server_->start_accept();
|
||||
// not print log
|
||||
server_->clear_access_channels(websocketpp::log::alevel::all);
|
||||
}
|
||||
}
|
||||
void do_decoder(std::map<websocketpp::connection_hdl, std::shared_ptr<FUNASR_MESSAGE>,std::owner_less<websocketpp::connection_hdl>>& data_map,
|
||||
std::vector<char>& buffer,
|
||||
websocketpp::connection_hdl& hdl,
|
||||
nlohmann::json& msg,
|
||||
std::vector<std::vector<std::string>>& punc_cache,
|
||||
std::vector<std::vector<float>> &hotwords_embedding,
|
||||
websocketpp::lib::mutex& thread_lock, bool& is_final,
|
||||
std::string wav_name,
|
||||
std::string modetype,
|
||||
bool itn,
|
||||
int audio_fs,
|
||||
std::string wav_format,
|
||||
FUNASR_HANDLE& tpass_online_handle,
|
||||
FUNASR_DEC_HANDLE& decoder_handle,
|
||||
std::string svs_lang,
|
||||
bool sys_itn);
|
||||
|
||||
void initAsr(std::map<std::string, std::string>& model_path, int thread_num);
|
||||
void on_message(websocketpp::connection_hdl hdl, message_ptr msg);
|
||||
void on_open(websocketpp::connection_hdl hdl);
|
||||
void on_close(websocketpp::connection_hdl hdl);
|
||||
context_ptr on_tls_init(tls_mode mode, websocketpp::connection_hdl hdl,
|
||||
std::string& s_certfile, std::string& s_keyfile);
|
||||
|
||||
private:
|
||||
void check_and_clean_connection();
|
||||
asio::io_context& io_decoder_; // threads for asr decoder
|
||||
// std::ofstream fout;
|
||||
// FUNASR_HANDLE asr_handle; // asr engine handle
|
||||
FUNASR_HANDLE tpass_handle=nullptr;
|
||||
bool isonline = true; // online or offline engine, now only support offline
|
||||
bool is_ssl = true;
|
||||
server* server_; // websocket server
|
||||
wss_server* wss_server_; // websocket server
|
||||
|
||||
// use map to keep the received samples data from one connection in offline
|
||||
// engine. if for online engline, a data struct is needed(TODO)
|
||||
|
||||
std::map<websocketpp::connection_hdl, std::shared_ptr<FUNASR_MESSAGE>,
|
||||
std::owner_less<websocketpp::connection_hdl>>
|
||||
data_map;
|
||||
websocketpp::lib::mutex m_lock; // mutex for sample_map
|
||||
};
|
||||
|
||||
#endif // WEBSOCKET_SERVER_H_
|
||||
@@ -0,0 +1,433 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaomingwork */
|
||||
|
||||
// websocket server for asr engine
|
||||
// take some ideas from https://github.com/k2-fsa/sherpa-onnx
|
||||
// online-websocket-server-impl.cc, thanks. The websocket server has two threads
|
||||
// pools, one for handle network data and one for asr decoder.
|
||||
// now only support offline engine.
|
||||
|
||||
#include "websocket-server.h"
|
||||
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
extern std::unordered_map<std::string, int> hws_map_;
|
||||
extern int fst_inc_wts_;
|
||||
extern float global_beam_, lattice_beam_, am_scale_;
|
||||
|
||||
context_ptr WebSocketServer::on_tls_init(tls_mode mode,
|
||||
websocketpp::connection_hdl hdl,
|
||||
std::string& s_certfile,
|
||||
std::string& s_keyfile) {
|
||||
namespace asio = websocketpp::lib::asio;
|
||||
|
||||
LOG(INFO) << "on_tls_init called with hdl: " << hdl.lock().get();
|
||||
LOG(INFO) << "using TLS mode: "
|
||||
<< (mode == MOZILLA_MODERN ? "Mozilla Modern"
|
||||
: "Mozilla Intermediate");
|
||||
|
||||
context_ptr ctx = websocketpp::lib::make_shared<asio::ssl::context>(
|
||||
asio::ssl::context::sslv23);
|
||||
|
||||
try {
|
||||
if (mode == MOZILLA_MODERN) {
|
||||
// Modern disables TLSv1
|
||||
ctx->set_options(
|
||||
asio::ssl::context::default_workarounds |
|
||||
asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3 |
|
||||
asio::ssl::context::no_tlsv1 | asio::ssl::context::single_dh_use);
|
||||
} else {
|
||||
ctx->set_options(asio::ssl::context::default_workarounds |
|
||||
asio::ssl::context::no_sslv2 |
|
||||
asio::ssl::context::no_sslv3 |
|
||||
asio::ssl::context::single_dh_use);
|
||||
}
|
||||
|
||||
ctx->use_certificate_chain_file(s_certfile);
|
||||
ctx->use_private_key_file(s_keyfile, asio::ssl::context::pem);
|
||||
|
||||
} catch (std::exception& e) {
|
||||
LOG(INFO) << "Exception: " << e.what();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// feed buffer to asr engine for decoder
|
||||
void WebSocketServer::do_decoder(const std::vector<char>& buffer,
|
||||
websocketpp::connection_hdl& hdl,
|
||||
nlohmann::json& msg,
|
||||
websocketpp::lib::mutex& thread_lock,
|
||||
std::vector<std::vector<float>> &hotwords_embedding,
|
||||
std::string wav_name,
|
||||
bool itn,
|
||||
int audio_fs,
|
||||
std::string wav_format,
|
||||
FUNASR_DEC_HANDLE& decoder_handle,
|
||||
std::string svs_lang,
|
||||
bool sys_itn) {
|
||||
try {
|
||||
int num_samples = buffer.size(); // the size of the buf
|
||||
|
||||
if (!buffer.empty() && hotwords_embedding.size() > 0) {
|
||||
std::string asr_result="";
|
||||
std::string stamp_res="";
|
||||
std::string stamp_sents="";
|
||||
try{
|
||||
FUNASR_RESULT Result = FunOfflineInferBuffer(
|
||||
asr_handle, buffer.data(), buffer.size(), RASR_NONE, nullptr,
|
||||
hotwords_embedding, audio_fs, wav_format, itn, decoder_handle,
|
||||
svs_lang, sys_itn);
|
||||
if (Result != nullptr){
|
||||
asr_result = FunASRGetResult(Result, 0); // get decode result
|
||||
stamp_res = FunASRGetStamp(Result);
|
||||
stamp_sents = FunASRGetStampSents(Result);
|
||||
FunASRFreeResult(Result);
|
||||
} else{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
LOG(ERROR) << "FUNASR_RESULT is nullptr.";
|
||||
}
|
||||
}catch (std::exception const& e) {
|
||||
LOG(ERROR) << e.what();
|
||||
}
|
||||
|
||||
websocketpp::lib::error_code ec;
|
||||
nlohmann::json jsonresult; // result json
|
||||
jsonresult["text"] = asr_result; // put result in 'text'
|
||||
jsonresult["mode"] = "offline";
|
||||
jsonresult["is_final"] = false;
|
||||
if(stamp_res != ""){
|
||||
jsonresult["timestamp"] = stamp_res;
|
||||
}
|
||||
if(stamp_sents != ""){
|
||||
try{
|
||||
nlohmann::json json_stamp = nlohmann::json::parse(stamp_sents);
|
||||
jsonresult["stamp_sents"] = json_stamp;
|
||||
}catch (std::exception const &e)
|
||||
{
|
||||
LOG(ERROR)<<e.what();
|
||||
jsonresult["stamp_sents"] = "";
|
||||
}
|
||||
}
|
||||
jsonresult["wav_name"] = wav_name;
|
||||
|
||||
// send the json to client
|
||||
if (is_ssl) {
|
||||
wss_server_->send(hdl, jsonresult.dump(),
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
} else {
|
||||
server_->send(hdl, jsonresult.dump(), websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
}
|
||||
|
||||
LOG(INFO) << "buffer.size=" << buffer.size() << ",result json=" << jsonresult.dump();
|
||||
}else{
|
||||
LOG(INFO) << "Sent empty msg";
|
||||
websocketpp::lib::error_code ec;
|
||||
nlohmann::json jsonresult; // result json
|
||||
jsonresult["text"] = ""; // put result in 'text'
|
||||
jsonresult["mode"] = "offline";
|
||||
jsonresult["is_final"] = false;
|
||||
jsonresult["wav_name"] = wav_name;
|
||||
|
||||
// send the json to client
|
||||
if (is_ssl) {
|
||||
wss_server_->send(hdl, jsonresult.dump(),
|
||||
websocketpp::frame::opcode::text, ec);
|
||||
} else {
|
||||
server_->send(hdl, jsonresult.dump(), websocketpp::frame::opcode::text,
|
||||
ec);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
scoped_lock guard(thread_lock);
|
||||
msg["access_num"]=(int)msg["access_num"]-1;
|
||||
}
|
||||
|
||||
void WebSocketServer::on_open(websocketpp::connection_hdl hdl) {
|
||||
scoped_lock guard(m_lock); // for threads safty
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg =
|
||||
std::make_shared<FUNASR_MESSAGE>(); // put a new data vector for new
|
||||
// connection
|
||||
data_msg->samples = std::make_shared<std::vector<char>>();
|
||||
data_msg->thread_lock = std::make_shared<websocketpp::lib::mutex>();
|
||||
data_msg->msg = nlohmann::json::parse("{}");
|
||||
data_msg->msg["wav_format"] = "pcm";
|
||||
data_msg->msg["wav_name"] = "wav-default-id";
|
||||
data_msg->msg["itn"] = true;
|
||||
data_msg->msg["audio_fs"] = 16000; // default is 16k
|
||||
data_msg->msg["access_num"] = 0; // the number of access for this object, when it is 0, we can free it saftly
|
||||
data_msg->msg["is_eof"]=false;
|
||||
data_msg->msg["svs_lang"]="auto";
|
||||
data_msg->msg["svs_itn"]=true;
|
||||
FUNASR_DEC_HANDLE decoder_handle =
|
||||
FunASRWfstDecoderInit(asr_handle, ASR_OFFLINE, global_beam_, lattice_beam_, am_scale_);
|
||||
data_msg->decoder_handle = decoder_handle;
|
||||
data_map.emplace(hdl, data_msg);
|
||||
LOG(INFO) << "on_open, active connections: " << data_map.size();
|
||||
}
|
||||
|
||||
void WebSocketServer::on_close(websocketpp::connection_hdl hdl) {
|
||||
scoped_lock guard(m_lock);
|
||||
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg = nullptr;
|
||||
auto it_data = data_map.find(hdl);
|
||||
if (it_data != data_map.end()) {
|
||||
data_msg = it_data->second;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
unique_lock guard_decoder(*(data_msg->thread_lock));
|
||||
data_msg->msg["is_eof"]=true;
|
||||
guard_decoder.unlock();
|
||||
|
||||
LOG(INFO) << "on_close, active connections: " << data_map.size();
|
||||
}
|
||||
|
||||
void remove_hdl(
|
||||
websocketpp::connection_hdl hdl,
|
||||
std::map<websocketpp::connection_hdl, std::shared_ptr<FUNASR_MESSAGE>,
|
||||
std::owner_less<websocketpp::connection_hdl>>& data_map) {
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg = nullptr;
|
||||
auto it_data = data_map.find(hdl);
|
||||
if (it_data != data_map.end()) {
|
||||
data_msg = it_data->second;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
unique_lock guard_decoder(*(data_msg->thread_lock));
|
||||
if (data_msg->msg["access_num"]==0 && data_msg->msg["is_eof"]==true) {
|
||||
FunWfstDecoderUnloadHwsRes(data_msg->decoder_handle);
|
||||
FunASRWfstDecoderUninit(data_msg->decoder_handle);
|
||||
data_msg->decoder_handle = nullptr;
|
||||
data_map.erase(hdl);
|
||||
LOG(INFO) << "remove one connection";
|
||||
}
|
||||
guard_decoder.unlock();
|
||||
}
|
||||
|
||||
void WebSocketServer::check_and_clean_connection() {
|
||||
while(true){
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
std::vector<websocketpp::connection_hdl> to_remove; // remove list
|
||||
auto iter = data_map.begin();
|
||||
while (iter != data_map.end()) { // loop to find closed connection
|
||||
websocketpp::connection_hdl hdl = iter->first;
|
||||
try{
|
||||
if (is_ssl) {
|
||||
wss_server::connection_ptr con = wss_server_->get_con_from_hdl(hdl);
|
||||
if (con->get_state() != 1) { // session::state::open ==1
|
||||
to_remove.push_back(hdl);
|
||||
}
|
||||
} else {
|
||||
server::connection_ptr con = server_->get_con_from_hdl(hdl);
|
||||
if (con->get_state() != 1) { // session::state::open ==1
|
||||
to_remove.push_back(hdl);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
// if connection is close, we set is_eof = true
|
||||
std::shared_ptr<FUNASR_MESSAGE> data_msg = nullptr;
|
||||
auto it_data = data_map.find(hdl);
|
||||
if (it_data != data_map.end()) {
|
||||
data_msg = it_data->second;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
unique_lock guard_decoder(*(data_msg->thread_lock));
|
||||
data_msg->msg["is_eof"]=true;
|
||||
guard_decoder.unlock();
|
||||
to_remove.push_back(hdl);
|
||||
LOG(INFO)<<"connection is closed.";
|
||||
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
for (auto hdl : to_remove) {
|
||||
{
|
||||
unique_lock lock(m_lock);
|
||||
remove_hdl(hdl, data_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketServer::on_message(websocketpp::connection_hdl hdl,
|
||||
message_ptr msg) {
|
||||
unique_lock lock(m_lock);
|
||||
// find the sample data vector according to one connection
|
||||
|
||||
std::shared_ptr<FUNASR_MESSAGE> msg_data = nullptr;
|
||||
|
||||
auto it_data = data_map.find(hdl);
|
||||
if (it_data != data_map.end()) {
|
||||
msg_data = it_data->second;
|
||||
if(msg_data->msg["is_eof"]){
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
} else{
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<char>> sample_data_p = msg_data->samples;
|
||||
std::shared_ptr<websocketpp::lib::mutex> thread_lock_p = msg_data->thread_lock;
|
||||
|
||||
lock.unlock();
|
||||
if (sample_data_p == nullptr) {
|
||||
LOG(INFO) << "error when fetch sample data vector";
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& payload = msg->get_payload(); // get msg type
|
||||
unique_lock guard_decoder(*(thread_lock_p)); // mutex for one connection
|
||||
switch (msg->get_opcode()) {
|
||||
case websocketpp::frame::opcode::text: {
|
||||
nlohmann::json jsonresult;
|
||||
try{
|
||||
jsonresult = nlohmann::json::parse(payload);
|
||||
}catch (std::exception const &e)
|
||||
{
|
||||
LOG(ERROR)<<e.what();
|
||||
msg_data->msg["is_eof"]=true;
|
||||
guard_decoder.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonresult["wav_name"] != nullptr) {
|
||||
msg_data->msg["wav_name"] = jsonresult["wav_name"];
|
||||
}
|
||||
if (jsonresult["wav_format"] != nullptr) {
|
||||
msg_data->msg["wav_format"] = jsonresult["wav_format"];
|
||||
}
|
||||
|
||||
// hotwords: fst/nn
|
||||
if(msg_data->hotwords_embedding == nullptr){
|
||||
std::unordered_map<std::string, int> merged_hws_map;
|
||||
std::string nn_hotwords = "";
|
||||
|
||||
if (jsonresult["hotwords"] != nullptr) {
|
||||
std::string json_string = jsonresult["hotwords"];
|
||||
if (!json_string.empty()){
|
||||
nlohmann::json json_fst_hws;
|
||||
try{
|
||||
json_fst_hws = nlohmann::json::parse(json_string);
|
||||
if(json_fst_hws.type() == nlohmann::json::value_t::object){
|
||||
// fst
|
||||
try{
|
||||
std::unordered_map<std::string, int> client_hws_map = json_fst_hws;
|
||||
merged_hws_map.insert(client_hws_map.begin(), client_hws_map.end());
|
||||
} catch (const std::exception& e) {
|
||||
LOG(INFO) << e.what();
|
||||
}
|
||||
}
|
||||
} catch (std::exception const &e)
|
||||
{
|
||||
LOG(ERROR)<<e.what();
|
||||
// nn
|
||||
std::string client_nn_hws = jsonresult["hotwords"];
|
||||
nn_hotwords += " " + client_nn_hws;
|
||||
// LOG(INFO) << "nn hotwords: " << client_nn_hws;
|
||||
}
|
||||
}
|
||||
}
|
||||
merged_hws_map.insert(hws_map_.begin(), hws_map_.end());
|
||||
|
||||
// fst
|
||||
LOG(INFO) << "hotwords: ";
|
||||
for (const auto& pair : merged_hws_map) {
|
||||
nn_hotwords += " " + pair.first;
|
||||
LOG(INFO) << pair.first << " : " << pair.second;
|
||||
}
|
||||
FunWfstDecoderLoadHwsRes(msg_data->decoder_handle, fst_inc_wts_, merged_hws_map);
|
||||
|
||||
// nn
|
||||
std::vector<std::vector<float>> new_hotwords_embedding= CompileHotwordEmbedding(asr_handle, nn_hotwords);
|
||||
msg_data->hotwords_embedding =
|
||||
std::make_shared<std::vector<std::vector<float>>>(new_hotwords_embedding);
|
||||
}
|
||||
if (jsonresult.contains("audio_fs")) {
|
||||
msg_data->msg["audio_fs"] = jsonresult["audio_fs"];
|
||||
}
|
||||
if (jsonresult.contains("itn")) {
|
||||
msg_data->msg["itn"] = jsonresult["itn"];
|
||||
}
|
||||
if (jsonresult.contains("svs_lang")) {
|
||||
msg_data->msg["svs_lang"] = jsonresult["svs_lang"];
|
||||
}
|
||||
if (jsonresult.contains("svs_itn")) {
|
||||
msg_data->msg["svs_itn"] = jsonresult["svs_itn"];
|
||||
}
|
||||
if ((jsonresult["is_speaking"] == false ||
|
||||
jsonresult["is_finished"] == true) &&
|
||||
msg_data->msg["is_eof"] != true &&
|
||||
msg_data->hotwords_embedding != nullptr) {
|
||||
LOG(INFO) << "client done";
|
||||
// for offline, send all receive data to decoder engine
|
||||
std::vector<std::vector<float>> hotwords_embedding_(*(msg_data->hotwords_embedding));
|
||||
asio::post(io_decoder_,
|
||||
std::bind(&WebSocketServer::do_decoder, this,
|
||||
std::move(*(sample_data_p.get())),
|
||||
std::move(hdl),
|
||||
std::ref(msg_data->msg),
|
||||
std::ref(*thread_lock_p),
|
||||
std::move(hotwords_embedding_),
|
||||
msg_data->msg["wav_name"],
|
||||
msg_data->msg["itn"],
|
||||
msg_data->msg["audio_fs"],
|
||||
msg_data->msg["wav_format"],
|
||||
std::ref(msg_data->decoder_handle),
|
||||
msg_data->msg["svs_lang"],
|
||||
msg_data->msg["svs_itn"]));
|
||||
msg_data->msg["access_num"]=(int)(msg_data->msg["access_num"])+1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case websocketpp::frame::opcode::binary: {
|
||||
// recived binary data
|
||||
const auto* pcm_data = static_cast<const char*>(payload.data());
|
||||
int32_t num_samples = payload.size();
|
||||
|
||||
if (isonline) {
|
||||
// TODO
|
||||
} else {
|
||||
// for offline, we add receive data to end of the sample data vector
|
||||
sample_data_p->insert(sample_data_p->end(), pcm_data,
|
||||
pcm_data + num_samples);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
guard_decoder.unlock();
|
||||
}
|
||||
|
||||
// init asr model
|
||||
void WebSocketServer::initAsr(std::map<std::string, std::string>& model_path,
|
||||
int thread_num, bool use_gpu, int batch_size) {
|
||||
try {
|
||||
// init model with api
|
||||
|
||||
asr_handle = FunOfflineInit(model_path, thread_num, use_gpu, batch_size);
|
||||
LOG(INFO) << "model successfully inited";
|
||||
|
||||
LOG(INFO) << "initAsr run check_and_clean_connection";
|
||||
std::thread clean_thread(&WebSocketServer::check_and_clean_connection,this);
|
||||
clean_thread.detach();
|
||||
LOG(INFO) << "initAsr run check_and_clean_connection finished";
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
LOG(INFO) << e.what();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaomingwork */
|
||||
|
||||
// websocket server for asr engine
|
||||
// take some ideas from https://github.com/k2-fsa/sherpa-onnx
|
||||
// online-websocket-server-impl.cc, thanks. The websocket server has two threads
|
||||
// pools, one for handle network data and one for asr decoder.
|
||||
// now only support offline engine.
|
||||
|
||||
#ifndef WEBSOCKET_SERVER_H_
|
||||
#define WEBSOCKET_SERVER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
#define ASIO_STANDALONE 1 // not boost
|
||||
#include <glog/logging.h>
|
||||
#include "util/text-utils.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <websocketpp/common/thread.hpp>
|
||||
#include <websocketpp/config/asio.hpp>
|
||||
#include <websocketpp/server.hpp>
|
||||
|
||||
#include "asio.hpp"
|
||||
#include "com-define.h"
|
||||
#include "funasrruntime.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "tclap/CmdLine.h"
|
||||
typedef websocketpp::server<websocketpp::config::asio> server;
|
||||
typedef websocketpp::server<websocketpp::config::asio_tls> wss_server;
|
||||
typedef server::message_ptr message_ptr;
|
||||
using websocketpp::lib::bind;
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::placeholders::_2;
|
||||
|
||||
typedef websocketpp::lib::lock_guard<websocketpp::lib::mutex> scoped_lock;
|
||||
typedef websocketpp::lib::unique_lock<websocketpp::lib::mutex> unique_lock;
|
||||
typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context>
|
||||
context_ptr;
|
||||
|
||||
typedef struct {
|
||||
std::string msg="";
|
||||
std::string stamp="";
|
||||
std::string stamp_sents;
|
||||
std::string tpass_msg="";
|
||||
float snippet_time=0;
|
||||
} FUNASR_RECOG_RESULT;
|
||||
|
||||
typedef struct {
|
||||
nlohmann::json msg;
|
||||
std::shared_ptr<std::vector<char>> samples;
|
||||
std::shared_ptr<std::vector<std::vector<float>>> hotwords_embedding=nullptr;
|
||||
std::shared_ptr<websocketpp::lib::mutex> thread_lock; // lock for each connection
|
||||
FUNASR_DEC_HANDLE decoder_handle=nullptr;
|
||||
} FUNASR_MESSAGE;
|
||||
|
||||
// See https://wiki.mozilla.org/Security/Server_Side_TLS for more details about
|
||||
// the TLS modes. The code below demonstrates how to implement both the modern
|
||||
enum tls_mode { MOZILLA_INTERMEDIATE = 1, MOZILLA_MODERN = 2 };
|
||||
class WebSocketServer {
|
||||
public:
|
||||
WebSocketServer(asio::io_context& io_decoder, bool is_ssl, server* server,
|
||||
wss_server* wss_server, std::string& s_certfile,
|
||||
std::string& s_keyfile)
|
||||
: io_decoder_(io_decoder),
|
||||
is_ssl(is_ssl),
|
||||
server_(server),
|
||||
wss_server_(wss_server){
|
||||
if (is_ssl) {
|
||||
std::cout << "certfile path is " << s_certfile << std::endl;
|
||||
wss_server->set_tls_init_handler(
|
||||
bind<context_ptr>(&WebSocketServer::on_tls_init, this,
|
||||
MOZILLA_INTERMEDIATE, ::_1, s_certfile, s_keyfile));
|
||||
wss_server_->set_message_handler(
|
||||
[this](websocketpp::connection_hdl hdl, message_ptr msg) {
|
||||
on_message(hdl, msg);
|
||||
});
|
||||
// set open handle
|
||||
wss_server_->set_open_handler(
|
||||
[this](websocketpp::connection_hdl hdl) { on_open(hdl); });
|
||||
// set close handle
|
||||
wss_server_->set_close_handler(
|
||||
[this](websocketpp::connection_hdl hdl) { on_close(hdl); });
|
||||
// begin accept
|
||||
wss_server_->start_accept();
|
||||
// not print log
|
||||
wss_server_->clear_access_channels(websocketpp::log::alevel::all);
|
||||
|
||||
} else {
|
||||
// set message handle
|
||||
server_->set_message_handler(
|
||||
[this](websocketpp::connection_hdl hdl, message_ptr msg) {
|
||||
on_message(hdl, msg);
|
||||
});
|
||||
// set open handle
|
||||
server_->set_open_handler(
|
||||
[this](websocketpp::connection_hdl hdl) { on_open(hdl); });
|
||||
// set close handle
|
||||
server_->set_close_handler(
|
||||
[this](websocketpp::connection_hdl hdl) { on_close(hdl); });
|
||||
// begin accept
|
||||
server_->start_accept();
|
||||
// not print log
|
||||
server_->clear_access_channels(websocketpp::log::alevel::all);
|
||||
}
|
||||
}
|
||||
void do_decoder(const std::vector<char>& buffer,
|
||||
websocketpp::connection_hdl& hdl,
|
||||
nlohmann::json& msg,
|
||||
websocketpp::lib::mutex& thread_lock,
|
||||
std::vector<std::vector<float>> &hotwords_embedding,
|
||||
std::string wav_name,
|
||||
bool itn,
|
||||
int audio_fs,
|
||||
std::string wav_format,
|
||||
FUNASR_DEC_HANDLE& decoder_handle,
|
||||
std::string svs_lang,
|
||||
bool sys_itn);
|
||||
|
||||
void initAsr(std::map<std::string, std::string>& model_path, int thread_num, bool use_gpu=false, int batch_size=1);
|
||||
void on_message(websocketpp::connection_hdl hdl, message_ptr msg);
|
||||
void on_open(websocketpp::connection_hdl hdl);
|
||||
void on_close(websocketpp::connection_hdl hdl);
|
||||
context_ptr on_tls_init(tls_mode mode, websocketpp::connection_hdl hdl,
|
||||
std::string& s_certfile, std::string& s_keyfile);
|
||||
|
||||
private:
|
||||
void check_and_clean_connection();
|
||||
asio::io_context& io_decoder_; // threads for asr decoder
|
||||
// std::ofstream fout;
|
||||
FUNASR_HANDLE asr_handle; // asr engine handle
|
||||
bool isonline = false; // online or offline engine, now only support offline
|
||||
bool is_ssl = true;
|
||||
server* server_; // websocket server
|
||||
wss_server* wss_server_; // websocket server
|
||||
|
||||
// use map to keep the received samples data from one connection in offline
|
||||
// engine. if for online engline, a data struct is needed(TODO)
|
||||
|
||||
std::map<websocketpp::connection_hdl, std::shared_ptr<FUNASR_MESSAGE>,
|
||||
std::owner_less<websocketpp::connection_hdl>>
|
||||
data_map;
|
||||
websocketpp::lib::mutex m_lock; // mutex for sample_map
|
||||
};
|
||||
|
||||
// std::unordered_map<std::string, int>& hws_map, int fst_inc_wts, std::string& nn_hotwords
|
||||
#endif // WEBSOCKET_SERVER_H_
|
||||
@@ -0,0 +1,2 @@
|
||||
阿里巴巴 20
|
||||
通义实验室 30
|
||||
@@ -0,0 +1,64 @@
|
||||
# Advanced Development Guide (File transcription service) ([click](../docs/SDK_advanced_guide_offline.md))
|
||||
# Real-time Speech Transcription Service Development Guide ([click](../docs/SDK_advanced_guide_online.md))
|
||||
|
||||
|
||||
# If you want to compile the file yourself, you can follow the steps below.
|
||||
## Building for Linux/Unix
|
||||
### Download onnxruntime
|
||||
```shell
|
||||
wget https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/onnxruntime-linux-x64-1.14.0.tgz
|
||||
tar -zxvf onnxruntime-linux-x64-1.14.0.tgz
|
||||
```
|
||||
|
||||
### Download ffmpeg
|
||||
```shell
|
||||
wget https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/ffmpeg-master-latest-linux64-gpl-shared.tar.xz
|
||||
tar -xvf ffmpeg-master-latest-linux64-gpl-shared.tar.xz
|
||||
```
|
||||
|
||||
### Install deps
|
||||
```shell
|
||||
# openblas
|
||||
sudo apt-get install libopenblas-dev #ubuntu
|
||||
# sudo yum -y install openblas-devel #centos
|
||||
|
||||
# openssl
|
||||
apt-get install libssl-dev #ubuntu
|
||||
# yum install openssl-devel #centos
|
||||
```
|
||||
|
||||
### Build runtime
|
||||
```shell
|
||||
git clone https://github.com/modelscope/FunASR.git && cd FunASR/runtime/websocket
|
||||
mkdir build && cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release .. -DONNXRUNTIME_DIR=/path/to/onnxruntime-linux-x64-1.14.0 -DFFMPEG_DIR=/path/to/ffmpeg-master-latest-linux64-gpl-shared
|
||||
make -j 4
|
||||
```
|
||||
|
||||
|
||||
## Building for Windows
|
||||
### Download onnxruntime
|
||||
https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/onnxruntime-win-x64-1.16.1.zip
|
||||
|
||||
Download to d:\onnxruntime-win-x64-1.16.1
|
||||
|
||||
### Download ffmpeg
|
||||
https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/ffmpeg-master-latest-win64-gpl-shared.zip
|
||||
|
||||
Download to d:\ffmpeg-master-latest-win64-gpl-shared
|
||||
|
||||
### Download openssl
|
||||
https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/openssl-1.1.1w.zip
|
||||
|
||||
Download to d:\openssl-1.1.1w
|
||||
|
||||
### Build runtime
|
||||
```
|
||||
git clone https://github.com/modelscope/FunASR.git
|
||||
cd FunASR/runtime/websocket
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../ -D OPENSSL_ROOT_DIR=d:/openssl-1.1.1w -D FFMPEG_DIR=d:/ffmpeg-master-latest-win64-gpl-shared -D ONNXRUNTIME_DIR=d:/onnxruntime-win-x64-1.16.1
|
||||
```
|
||||
Open FunASR/runtime/websocket/build/FunASRWebscoket.sln in Visual Studio and complete the compilation.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# FunASR离线文件转写服务开发指南([点击此处](../docs/SDK_advanced_guide_offline_zh.md))
|
||||
|
||||
# FunASR实时语音听写服务开发指南([点击此处](../docs/SDK_advanced_guide_online_zh.md))
|
||||
|
||||
# 如果您想自己编译文件,可以参考下述步骤
|
||||
## Linux/Unix 平台编译
|
||||
### 下载 onnxruntime
|
||||
```shell
|
||||
wget https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/onnxruntime-linux-x64-1.14.0.tgz
|
||||
tar -zxvf onnxruntime-linux-x64-1.14.0.tgz
|
||||
```
|
||||
|
||||
### 下载 ffmpeg
|
||||
```shell
|
||||
wget https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/ffmpeg-master-latest-linux64-gpl-shared.tar.xz
|
||||
tar -xvf ffmpeg-master-latest-linux64-gpl-shared.tar.xz
|
||||
```
|
||||
|
||||
### 安装依赖
|
||||
```shell
|
||||
# openblas
|
||||
sudo apt-get install libopenblas-dev #ubuntu
|
||||
# sudo yum -y install openblas-devel #centos
|
||||
|
||||
# openssl
|
||||
apt-get install libssl-dev #ubuntu
|
||||
# yum install openssl-devel #centos
|
||||
```
|
||||
|
||||
### 编译 runtime
|
||||
|
||||
```shell
|
||||
git clone https://github.com/modelscope/FunASR.git && cd FunASR/runtime/websocket
|
||||
mkdir build && cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release .. -DONNXRUNTIME_DIR=/path/to/onnxruntime-linux-x64-1.14.0 -DFFMPEG_DIR=/path/to/ffmpeg-master-latest-linux64-gpl-shared
|
||||
make -j 4
|
||||
```
|
||||
|
||||
|
||||
## Windows 平台编译
|
||||
### 下载 onnxruntime
|
||||
https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/onnxruntime-win-x64-1.16.1.zip
|
||||
|
||||
下载并解压到 d:/onnxruntime-win-x64-1.16.1
|
||||
|
||||
### 下载 ffmpeg
|
||||
https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/ffmpeg-master-latest-win64-gpl-shared.zip
|
||||
|
||||
下载并解压到 d:/ffmpeg-master-latest-win64-gpl-shared
|
||||
|
||||
### 编译 openssl
|
||||
https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/openssl-1.1.1w.zip
|
||||
|
||||
下载解压到 d:/openssl-1.1.1w
|
||||
|
||||
### 编译 runtime
|
||||
```
|
||||
git clone https://github.com/modelscope/FunASR.git
|
||||
cd FunASR/runtime/websocket
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../ -D OPENSSL_ROOT_DIR=d:/openssl-1.1.1w -D FFMPEG_DIR=d:/ffmpeg-master-latest-win64-gpl-shared -D ONNXRUNTIME_DIR=d:/onnxruntime-win-x64-1.16.1
|
||||
```
|
||||
Visual Studio 打开 FunASR/runtime/websocket/build/FunASRWebscoket.sln 完成编译;
|
||||
编译后的可执行文件位于:FunASR/runtime/websocket/build/bin/Debug;
|
||||
从 onnxruntime-win-x64-1.16.1/lib, ffmpeg-master-latest-win64-gpl-shared/bin, openssl-1.1.1w/bin copy相关的DLL库至: FunASR/runtime/websocket/build/bin/Debug
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#### Download onnxruntime
|
||||
```shell
|
||||
bash third_party/download_onnxruntime.sh
|
||||
```
|
||||
|
||||
#### Download ffmpeg
|
||||
```shell
|
||||
bash third_party/download_ffmpeg.sh
|
||||
```
|
||||
|
||||
#### Install openblas and openssl
|
||||
```shell
|
||||
sudo apt-get install libopenblas-dev libssl-dev #ubuntu
|
||||
# sudo yum -y install openblas-devel openssl-devel #centos
|
||||
```
|
||||
Reference in New Issue
Block a user