Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions c-api-examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ target_link_libraries(sense-voice-c-api sherpa-onnx-c-api)
add_executable(funasr-nano-c-api funasr-nano-c-api.c)
target_link_libraries(funasr-nano-c-api sherpa-onnx-c-api)

add_executable(qwen3-asr-c-api qwen3-asr-c-api.c)
target_link_libraries(qwen3-asr-c-api sherpa-onnx-c-api)

add_executable(sense-voice-with-hr-c-api sense-voice-with-hr-c-api.c)
target_link_libraries(sense-voice-with-hr-c-api sherpa-onnx-c-api)

Expand Down
87 changes: 87 additions & 0 deletions c-api-examples/qwen3-asr-c-api.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// c-api-examples/qwen3-asr-c-api.c
//
// Copyright (c) 2026 zengyw
//
// Offline Qwen3-ASR using sherpa-onnx C API (conv_frontend + encoder + decoder
// with KV cache; tokenizer directory).
//
// clang-format off
//
// Prepare a local model directory with:
// conv_frontend.onnx, encoder.onnx, decoder.onnx, tokenizer/ (vocab.json, ...)
// and a 16-bit PCM mono WAV, then adjust paths below.
//
// clang-format on

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sherpa-onnx/c-api/c-api.h"

int32_t main() {
// clang-format off
const char *wav_filename = "./test.wav";
const char *conv_frontend = "./model/conv_frontend.onnx";
const char *encoder = "./model/encoder.onnx";
const char *decoder = "./model/decoder.onnx";
const char *tokenizer = "./model/tokenizer";
// clang-format on

const SherpaOnnxWave *wave = SherpaOnnxReadWave(wav_filename);
if (wave == NULL) {
fprintf(stderr, "Failed to read %s\n", wav_filename);
return -1;
}

SherpaOnnxOfflineQwen3ASRModelConfig qwen3;
memset(&qwen3, 0, sizeof(qwen3));
qwen3.conv_frontend = conv_frontend;
qwen3.encoder = encoder;
qwen3.decoder = decoder;
qwen3.tokenizer = tokenizer;
qwen3.max_total_len = 512;
qwen3.max_new_tokens = 64;
qwen3.temperature = 1e-6f;
qwen3.top_p = 0.8f;
qwen3.seed = 42;

SherpaOnnxOfflineModelConfig offline_model_config;
memset(&offline_model_config, 0, sizeof(offline_model_config));
offline_model_config.debug = 1;
offline_model_config.num_threads = 2;
offline_model_config.provider = "cpu";
offline_model_config.qwen3_asr = qwen3;

SherpaOnnxOfflineRecognizerConfig recognizer_config;
memset(&recognizer_config, 0, sizeof(recognizer_config));
recognizer_config.decoding_method = "greedy_search";
recognizer_config.model_config = offline_model_config;

const SherpaOnnxOfflineRecognizer *recognizer =
SherpaOnnxCreateOfflineRecognizer(&recognizer_config);

if (recognizer == NULL) {
fprintf(stderr, "Please check your config!\n");
SherpaOnnxFreeWave(wave);
return -1;
}

const SherpaOnnxOfflineStream *stream =
SherpaOnnxCreateOfflineStream(recognizer);

SherpaOnnxAcceptWaveformOffline(stream, wave->sample_rate, wave->samples,
wave->num_samples);
SherpaOnnxDecodeOfflineStream(recognizer, stream);
const SherpaOnnxOfflineRecognizerResult *result =
SherpaOnnxGetOfflineStreamResult(stream);

fprintf(stderr, "Decoded text: %s\n", result->text);

SherpaOnnxDestroyOfflineRecognizerResult(result);
SherpaOnnxDestroyOfflineStream(stream);
SherpaOnnxDestroyOfflineRecognizer(recognizer);
SherpaOnnxFreeWave(wave);

return 0;
}
3 changes: 3 additions & 0 deletions cxx-api-examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ target_link_libraries(vad-cxx-api sherpa-onnx-cxx-api)
add_executable(funasr-nano-cxx-api ./funasr-nano-cxx-api.cc)
target_link_libraries(funasr-nano-cxx-api sherpa-onnx-cxx-api)

add_executable(qwen3-asr-cxx-api ./qwen3-asr-cxx-api.cc)
target_link_libraries(qwen3-asr-cxx-api sherpa-onnx-cxx-api)

if(SHERPA_ONNX_ENABLE_TTS)
add_executable(matcha-tts-zh-cxx-api ./matcha-tts-zh-cxx-api.cc)
target_link_libraries(matcha-tts-zh-cxx-api sherpa-onnx-cxx-api)
Expand Down
88 changes: 88 additions & 0 deletions cxx-api-examples/qwen3-asr-cxx-api.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// cxx-api-examples/qwen3-asr-cxx-api.cc
//
// Copyright (c) 2026 zengyw
//
// Offline Qwen3-ASR using sherpa-onnx C++ API wrapper.
//
// clang-format off
//
// Usage:
// Adjust paths to conv_frontend.onnx, encoder.onnx, decoder.onnx, tokenizer/
// and input WAV, then build and run this example.
//
// clang-format on

#include <chrono>
#include <cstdio>
#include <iostream>
#include <string>

#include "sherpa-onnx/c-api/cxx-api.h"

int32_t main(int32_t argc, char *argv[]) {
using namespace sherpa_onnx::cxx;

OfflineRecognizerConfig config;
config.model_config.num_threads = 2;
config.model_config.debug = false;
config.model_config.provider = "cpu";

// clang-format off
config.model_config.qwen3_asr.conv_frontend = "./model/conv_frontend.onnx";
config.model_config.qwen3_asr.encoder = "./model/encoder.onnx";
config.model_config.qwen3_asr.decoder = "./model/decoder.onnx";
config.model_config.qwen3_asr.tokenizer = "./model/tokenizer";
config.model_config.qwen3_asr.max_total_len = 512;
config.model_config.qwen3_asr.max_new_tokens = 64;
config.model_config.qwen3_asr.temperature = 1e-6f;
config.model_config.qwen3_asr.top_p = 0.8f;
config.model_config.qwen3_asr.seed = 42;
// clang-format on

std::string wave_filename = "./test.wav";
if (argc >= 2) {
wave_filename = argv[1];
}

std::cout << "Loading model\n";
OfflineRecognizer recognizer = OfflineRecognizer::Create(config);
if (!recognizer.Get()) {
std::cerr << "Please check your config\n";
return -1;
}
std::cout << "Loading model done\n";

Wave wave = ReadWave(wave_filename);
if (wave.samples.empty()) {
std::cerr << "Failed to read: '" << wave_filename << "'\n";
return -1;
}

std::cout << "Start recognition\n";
const auto begin = std::chrono::steady_clock::now();

OfflineStream stream = recognizer.CreateStream();
stream.AcceptWaveform(wave.sample_rate, wave.samples.data(),
wave.samples.size());

recognizer.Decode(&stream);

OfflineRecognizerResult result = recognizer.GetResult(&stream);

const auto end = std::chrono::steady_clock::now();
const float elapsed_seconds =
std::chrono::duration_cast<std::chrono::milliseconds>(end - begin)
.count() /
1000.;
float duration = wave.samples.size() / static_cast<float>(wave.sample_rate);
float rtf = elapsed_seconds / duration;

std::cout << "text: " << result.text << "\n";
printf("Number of threads: %d\n", config.model_config.num_threads);
printf("Duration: %.3fs\n", duration);
printf("Elapsed seconds: %.3fs\n", elapsed_seconds);
printf("(Real time factor) RTF = %.3f / %.3f = %.3f\n", elapsed_seconds,
duration, rtf);

return 0;
}
191 changes: 191 additions & 0 deletions python-api-examples/offline-qwen3-asr-decode-files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#!/usr/bin/env python3
#
# Copyright (c) 2026 zengyw
#
"""
Decode audio files using Qwen3-ASR with sherpa-onnx Python API.

Usage:
python offline-qwen3-asr-decode-files.py \\
--conv-frontend=/path/to/conv_frontend.onnx \\
--encoder=/path/to/encoder.onnx \\
--decoder=/path/to/decoder.onnx \\
--tokenizer=/path/to/tokenizer_dir \\
[--num-threads=4] \\
[--provider=cpu] \\
audio1.wav audio2.wav ...
"""

import argparse
import sys
from pathlib import Path

import soundfile as sf

try:
import sherpa_onnx
except ImportError:
print("Please install sherpa-onnx: pip install sherpa-onnx")
sys.exit(1)


def get_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__doc__,
)

parser.add_argument(
"--conv-frontend",
type=str,
required=True,
help="Path to conv_frontend.onnx",
)

parser.add_argument(
"--encoder",
type=str,
required=True,
help="Path to encoder.onnx",
)

parser.add_argument(
"--decoder",
type=str,
required=True,
help="Path to decoder.onnx (KV cache LLM)",
)

parser.add_argument(
"--tokenizer",
type=str,
required=True,
help="Path to tokenizer directory (vocab.json, merges.txt, ...)",
)

parser.add_argument(
"--max-total-len",
type=int,
default=512,
help="Maximum KV cache sequence length",
)

parser.add_argument(
"--max-new-tokens",
type=int,
default=64,
help="Maximum number of new tokens to generate",
)

parser.add_argument(
"--temperature",
type=float,
default=1e-6,
help="Sampling temperature",
)

parser.add_argument(
"--top-p",
type=float,
default=0.8,
help="Top-p (nucleus) sampling threshold",
)

parser.add_argument(
"--seed",
type=int,
default=42,
help="Random seed",
)

parser.add_argument(
"--num-threads",
type=int,
default=2,
help="Number of threads for neural network computation",
)

parser.add_argument(
"--sample-rate",
type=int,
default=16000,
help="Input audio sample rate for feature extractor",
)

parser.add_argument(
"--feature-dim",
type=int,
default=128,
help="Mel feature dimension (Qwen3-ASR offline uses 128)",
)

parser.add_argument(
"--provider",
type=str,
default="cpu",
choices=["cpu", "cuda"],
help="Provider: cpu or cuda",
)

parser.add_argument(
"--debug",
action="store_true",
help="True to print model information while loading",
)

parser.add_argument(
"sound_files",
type=str,
nargs="+",
help="Input wav file(s), single-channel 16-bit PCM; sample rate arbitrary.",
)

return parser.parse_args()


def create_recognizer(args) -> sherpa_onnx.OfflineRecognizer:
return sherpa_onnx.OfflineRecognizer.from_qwen3_asr(
conv_frontend=args.conv_frontend,
encoder=args.encoder,
decoder=args.decoder,
tokenizer=args.tokenizer,
num_threads=args.num_threads,
sample_rate=args.sample_rate,
feature_dim=args.feature_dim,
provider=args.provider,
debug=args.debug,
max_total_len=args.max_total_len,
max_new_tokens=args.max_new_tokens,
temperature=args.temperature,
top_p=args.top_p,
seed=args.seed,
)


def decode_file(recognizer: sherpa_onnx.OfflineRecognizer, filename: str):
audio, sample_rate = sf.read(filename, dtype="float32", always_2d=True)
audio = audio[:, 0]

stream = recognizer.create_stream()
stream.accept_waveform(sample_rate, audio)
recognizer.decode_stream(stream)
return stream.result


def main():
args = get_args()
print("Creating recognizer...")
recognizer = create_recognizer(args)
print("Recognizer created!")
print(recognizer.config)

for f in args.sound_files:
if not Path(f).is_file():
print(f"Skip missing file: {f}", file=sys.stderr)
continue
result = decode_file(recognizer, f)
print(f"{f}\n text: {result.text}\n")


if __name__ == "__main__":
main()
Loading
Loading