Skip to content

Commit 4648bed

Browse files
manickavela29manickavela-uni
authored andcommitted
refactor and pybind
-- refactored class to support pybind -- signal and interupt handling for gracefull shutdown
1 parent bab5566 commit 4648bed

File tree

10 files changed

+390
-63
lines changed

10 files changed

+390
-63
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2025 Uniphore
3+
4+
'''
5+
Real-time speech recognition server using WebSockets.
6+
Python API interface to start the server.
7+
Python wrapper around implementation of online-websocket-server.cc in C++.
8+
9+
(1) Download streaming transducer model
10+
11+
curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-streaming-zipformer-en-2023-06-26.tar.bz2
12+
tar xvf sherpa-onnx-streaming-zipformer-en-2023-06-26.tar.bz2
13+
rm sherpa-onnx-streaming-zipformer-en-2023-06-26.tar.bz2
14+
15+
(2) Starting websocket server using the downloaded model
16+
17+
python3 ./python-api-examples/online-websocket-server.py \
18+
--tokens=./sherpa-onnx-streaming-zipformer-en-2023-06-26/tokens.txt \
19+
--encoder=./sherpa-onnx-streaming-zipformer-en-2023-06-26/encoder-epoch-99-avg-1-chunk-16-left-128.onnx \
20+
--decoder=./sherpa-onnx-streaming-zipformer-en-2023-06-26/decoder-epoch-99-avg-1-chunk-16-left-128.onnx \
21+
--joiner=./sherpa-onnx-streaming-zipformer-en-2023-06-26/joiner-epoch-99-avg-1-chunk-16-left-128.onnx \
22+
--max-batch-size=5 \
23+
--loop-interval-ms=10
24+
25+
'''
26+
import argparse
27+
import sys
28+
import signal
29+
from sherpa_onnx import OnlineWebSocketServer
30+
31+
def signal_handler(sig, frame):
32+
print('Exiting...')
33+
sys.exit(0)
34+
35+
# Bind SIGINT to signal_handler
36+
signal.signal(signal.SIGINT, signal_handler)
37+
38+
if __name__ == "__main__":
39+
args = sys.argv[:]
40+
OnlineWebSocketServer(server_args=args)

sherpa-onnx/csrc/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ set(sources
8787
online-transducer-model.cc
8888
online-transducer-modified-beam-search-decoder.cc
8989
online-transducer-nemo-model.cc
90+
online-transducer-greedy-search-nemo-decoder.cc
91+
online-websocket-server.cc
92+
online-websocket-server-impl.cc
9093
online-wenet-ctc-model-config.cc
9194
online-wenet-ctc-model.cc
9295
online-zipformer-transducer-model.cc
Lines changed: 93 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// sherpa-onnx/csrc/online-websocket-server.cc
22
//
33
// Copyright (c) 2022-2023 Xiaomi Corporation
4+
// Copyright (c) 2025 Uniphore (Author: Manickavela A)
45

5-
#include "asio.hpp"
6-
#include "sherpa-onnx/csrc/macros.h"
7-
#include "sherpa-onnx/csrc/online-websocket-server-impl.h"
8-
#include "sherpa-onnx/csrc/parse-options.h"
6+
#include <string>
7+
#include <csignal>
8+
9+
#include "sherpa-onnx/csrc/online-websocket-server.h"
910

1011
static constexpr const char *kUsageMessage = R"(
1112
Automatic speech recognition with sherpa-onnx using websocket.
@@ -30,92 +31,121 @@ Please refer to
3031
for a list of pre-trained models to download.
3132
)";
3233

33-
class OnlineWebsocketServerApp {
34-
public:
35-
OnlineWebsocketServerApp(int32_t argc, char *argv[]) : argc_(argc), argv_(argv) {}
3634

37-
void Run() {
38-
sherpa_onnx::ParseOptions po(kUsageMessage);
39-
sherpa_onnx::OnlineWebsocketServerConfig config;
35+
// Global server instance pointer for signal handling
36+
OnlineWebsocketServerApp *global_server_instance = nullptr;
4037

41-
// the server will listen on this port
42-
int32_t port = 6006;
38+
// Signal handler to stop the server
39+
void SignalHandler(int signal) {
40+
if (signal == SIGINT || signal == SIGTERM) {
41+
SHERPA_ONNX_LOGE("\nSignal %d received. Stopping server...", signal);
42+
if (global_server_instance) {
43+
global_server_instance->Stop();
44+
}
45+
}
46+
}
4347

44-
// size of the thread pool for handling network connections
45-
int32_t num_io_threads = 1;
48+
OnlineWebsocketServerApp::OnlineWebsocketServerApp(
49+
int32_t argc, char *argv[]) : argc_(argc), argv_(argv) {}
4650

47-
// size of the thread pool for neural network computation and decoding
48-
int32_t num_work_threads = 3;
51+
void OnlineWebsocketServerApp::Run() {
52+
sherpa_onnx::ParseOptions po(kUsageMessage);
53+
sherpa_onnx::OnlineWebsocketServerConfig config;
4954

50-
po.Register("num-io-threads", &num_io_threads,
51-
"Thread pool size for network connections.");
55+
// the server will listen on this port
56+
int32_t port = 6006;
5257

53-
po.Register("num-work-threads", &num_work_threads,
54-
"Thread pool size for for neural network "
55-
"computation and decoding.");
58+
// size of the thread pool for handling network connections
59+
int32_t num_io_threads = 1;
5660

57-
po.Register("port", &port, "The port on which the server will listen.");
61+
// size of the thread pool for neural network computation and decoding
62+
int32_t num_work_threads = 3;
5863

59-
config.Register(&po);
64+
po.Register("num-io-threads", &num_io_threads,
65+
"Thread pool size for network connections.");
6066

61-
if (argc_ == 1) {
62-
po.PrintUsage();
63-
exit(EXIT_FAILURE);
64-
}
67+
po.Register("num-work-threads", &num_work_threads,
68+
"Thread pool size for for neural network "
69+
"computation and decoding.");
6570

66-
po.Read(argc_, argv_);
71+
po.Register("port", &port, "The port on which the server will listen.");
6772

68-
if (po.NumArgs() != 0) {
69-
SHERPA_ONNX_LOGE("Unrecognized positional arguments!");
70-
po.PrintUsage();
71-
exit(EXIT_FAILURE);
72-
}
73+
config.Register(&po);
7374

74-
config.Validate();
75+
if (argc_ == 1) {
76+
po.PrintUsage();
77+
exit(EXIT_FAILURE);
78+
}
7579

76-
asio::io_context io_conn; // for network connections
77-
asio::io_context io_work; // for neural network and decoding
80+
po.Read(argc_, argv_);
7881

79-
sherpa_onnx::OnlineWebsocketServer server(io_conn, io_work, config);
80-
server.Run(port);
82+
if (po.NumArgs() != 0) {
83+
SHERPA_ONNX_LOGE("Unrecognized positional arguments!");
84+
po.PrintUsage();
85+
exit(EXIT_FAILURE);
86+
}
8187

82-
SHERPA_ONNX_LOGE("Started!");
83-
SHERPA_ONNX_LOGE("Listening on: %d", port);
84-
SHERPA_ONNX_LOGE("Number of work threads: %d", num_work_threads);
88+
config.Validate();
8589

86-
// give some work to do for the io_work pool
87-
auto work_guard = asio::make_work_guard(io_work);
90+
// Set the global instance for signal handling
91+
global_server_instance = this;
8892

89-
std::vector<std::thread> io_threads;
93+
// Handle SIGINT and SIGTERM
94+
std::signal(SIGINT, SignalHandler);
95+
std::signal(SIGTERM, SignalHandler);
9096

91-
// decrement since the main thread is also used for network communications
92-
for (int32_t i = 0; i < num_io_threads - 1; ++i) {
93-
io_threads.emplace_back([&io_conn]() { io_conn.run(); });
94-
}
97+
// io_conn for network connections
98+
// io_work for neural network and decoding
9599

96-
std::vector<std::thread> work_threads;
97-
for (int32_t i = 0; i < num_work_threads; ++i) {
98-
work_threads.emplace_back([&io_work]() { io_work.run(); });
99-
}
100+
sherpa_onnx::OnlineWebsocketServer server(io_conn_, io_work_, config);
101+
server.Run(port);
100102

101-
io_conn.run();
103+
SHERPA_ONNX_LOGE("Started!");
104+
SHERPA_ONNX_LOGE("Listening on: %d", port);
105+
SHERPA_ONNX_LOGE("Number of work threads: %d", num_work_threads);
102106

103-
for (auto &t : io_threads) {
104-
t.join();
105-
}
107+
// give some work to do for the io_work pool
108+
auto work_guard = asio::make_work_guard(io_work_);
106109

107-
for (auto &t : work_threads) {
108-
t.join();
109-
}
110+
std::vector<std::thread> io_threads;
111+
112+
// decrement since the main thread is also used for network communications
113+
for (int32_t i = 0; i < num_io_threads - 1; ++i) {
114+
io_threads.emplace_back([this]() { io_conn_.run(); });
110115
}
111116

112-
private:
113-
int32_t argc_;
114-
char **argv_;
115-
};
117+
std::vector<std::thread> work_threads;
118+
for (int32_t i = 0; i < num_work_threads; ++i) {
119+
work_threads.emplace_back([this]() { io_work_.run(); });
120+
}
121+
122+
// Main thread handles IO
123+
io_conn_.run();
124+
125+
for (auto &t : io_threads) {
126+
t.join();
127+
}
128+
129+
for (auto &t : work_threads) {
130+
t.join();
131+
}
132+
SHERPA_ONNX_LOGE("Server shut down gracefully.");
133+
}
134+
135+
void OnlineWebsocketServerApp::Stop() {
136+
shutdown_requested_.store(true);
137+
io_conn_.stop();
138+
io_work_.stop();
139+
SHERPA_ONNX_LOGE("Server stopping...");
140+
}
116141

117142
int32_t main(int32_t argc, char *argv[]) {
118143
OnlineWebsocketServerApp app(argc, argv);
119144
app.Run();
120145
return 0;
121146
}
147+
148+
void StartServer(int32_t argc, char *argv[]) {
149+
OnlineWebsocketServerApp app(argc, argv);
150+
app.Run();
151+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// sherpa-onnx/csrc/online-wenet-ctc-model-config.h
2+
//
3+
// Copyright (c) 2025 Uniphore (Author: Manickavela A)s
4+
5+
#ifndef SHERPA_ONNX_ONLINE_WEBSOCKET_SERVER_H
6+
#define SHERPA_ONNX_ONLINE_WEBSOCKET_SERVER_H
7+
8+
#include <asio.hpp>
9+
#include <thread>
10+
#include "sherpa-onnx/csrc/macros.h"
11+
#include "sherpa-onnx/csrc/online-websocket-server-impl.h"
12+
#include "sherpa-onnx/csrc/parse-options.h"
13+
14+
class OnlineWebsocketServerApp {
15+
public:
16+
OnlineWebsocketServerApp(int32_t argc, char *argv[]);
17+
void Run();
18+
void Stop();
19+
20+
private:
21+
int32_t argc_;
22+
char **argv_;
23+
asio::io_context io_conn_; // ASIO context for connections
24+
asio::io_context io_work_; // ASIO context for work
25+
std::atomic<bool> shutdown_requested_{false};
26+
std::vector<std::thread> io_threads_;
27+
std::vector<std::thread> work_threads_;
28+
};
29+
30+
// Declare StartServer so it's accessible for Pybind
31+
void StartServer(int32_t argc, char *argv[]);
32+
33+
#endif // SHERPA_ONNX_ONLINE_WEBSOCKET_SERVER_H

sherpa-onnx/python/csrc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ set(srcs
3535
online-transducer-model-config.cc
3636
online-wenet-ctc-model-config.cc
3737
online-zipformer2-ctc-model-config.cc
38+
online-websocket-server-app.cc
3839
provider-config.cc
3940
sherpa-onnx.cc
4041
silero-vad-model-config.cc
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// sherpa-onnx/python/csrc/online-websocket-server.cc
2+
//
3+
// Copyright (c) 2025 Uniphore (Author: Manickavela A)
4+
5+
#include "sherpa-onnx/python/csrc/online-websocket-server-app.h"
6+
7+
#include <string>
8+
9+
#include "asio.hpp"
10+
#include "sherpa-onnx/csrc/online-websocket-server.h"
11+
#include "sherpa-onnx/csrc/macros.h"
12+
13+
namespace sherpa_onnx {
14+
15+
void StartServerWrapper(py::list args) {
16+
int argc = args.size();
17+
std::vector<std::string> args_str; // Store actual strings
18+
std::vector<char *> argv; // Store pointers to those strings
19+
20+
for (const auto &arg : args) {
21+
args_str.push_back(arg.cast<std::string>());
22+
}
23+
24+
// Fill argv with pointers to the actual string data
25+
for (auto &str : args_str) {
26+
argv.push_back(str.data());
27+
}
28+
29+
argv.push_back(nullptr); // Null-terminate like C-style arrays
30+
31+
// Call your server
32+
StartServer(argc, argv.data());
33+
}
34+
35+
void PybindOnlineWebsocketServerWrapperApp(py::module *m) {
36+
m->def("start_server", &StartServerWrapper, "Start the WebSocket server",
37+
py::call_guard<py::gil_scoped_release>());
38+
}
39+
40+
} // namespace sherpa_onnx
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// sherpa-onnx/python/csrc/online-websocket-server.h
2+
//
3+
// Copyright (c) 2025 Uniphore (Author: Manickavela A)
4+
5+
#ifndef SHERPA_ONNX_PYTHON_CSRC_ONLINE_WEBSOCKET_SERVER_APP_H_
6+
#define SHERPA_ONNX_PYTHON_CSRC_ONLINE_WEBSOCKET_SERVER_APP_H_
7+
8+
#include "sherpa-onnx/python/csrc/sherpa-onnx.h"
9+
10+
11+
namespace sherpa_onnx {
12+
13+
void PybindOnlineWebsocketServerWrapperApp(py::module *m);
14+
15+
}
16+
#endif // SHERPA_ONNX_PYTHON_CSRC_ONLINE_WEBSOCKET_SERVER_H_

sherpa-onnx/python/csrc/sherpa-onnx.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "sherpa-onnx/python/csrc/online-punctuation.h"
2424
#include "sherpa-onnx/python/csrc/online-recognizer.h"
2525
#include "sherpa-onnx/python/csrc/online-stream.h"
26+
#include "sherpa-onnx/python/csrc/online-websocket-server-app.h"
2627
#include "sherpa-onnx/python/csrc/speaker-embedding-extractor.h"
2728
#include "sherpa-onnx/python/csrc/speaker-embedding-manager.h"
2829
#include "sherpa-onnx/python/csrc/spoken-language-identification.h"
@@ -56,6 +57,7 @@ PYBIND11_MODULE(_sherpa_onnx, m) {
5657
PybindOnlineModelConfig(&m);
5758
PybindOnlineLMConfig(&m);
5859
PybindOnlineStream(&m);
60+
PybindOnlineWebsocketServerWrapperApp(&m);
5961
PybindEndpoint(&m);
6062
PybindOnlineRecognizer(&m);
6163
PybindKeywordSpotter(&m);

sherpa-onnx/python/sherpa_onnx/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,5 @@
4646
from .keyword_spotter import KeywordSpotter
4747
from .offline_recognizer import OfflineRecognizer
4848
from .online_recognizer import OnlineRecognizer
49+
from .online_websocket_server import OnlineWebSocketServer
4950
from .utils import text2token

0 commit comments

Comments
 (0)