diff --git a/c-api-examples/CMakeLists.txt b/c-api-examples/CMakeLists.txt index 98bd4eb46a..05f322747c 100644 --- a/c-api-examples/CMakeLists.txt +++ b/c-api-examples/CMakeLists.txt @@ -28,6 +28,9 @@ if(SHERPA_ONNX_ENABLE_TTS) add_executable(kokoro-tts-zh-en-c-api kokoro-tts-zh-en-c-api.c) target_link_libraries(kokoro-tts-zh-en-c-api sherpa-onnx-c-api) + + add_executable(pocket-tts-en-c-api pocket-tts-en-c-api.c) + target_link_libraries(pocket-tts-en-c-api sherpa-onnx-c-api) endif() if(SHERPA_ONNX_ENABLE_SPEAKER_DIARIZATION) diff --git a/c-api-examples/pocket-tts-en-c-api.c b/c-api-examples/pocket-tts-en-c-api.c new file mode 100644 index 0000000000..7228deb79a --- /dev/null +++ b/c-api-examples/pocket-tts-en-c-api.c @@ -0,0 +1,113 @@ +// c-api-examples/pocket-tts-en-c-api.c +// +// Copyright (c) 2026 Xiaoyingtao Corporation + +// This file shows how to use sherpa-onnx C API +// for English TTS with Pocket TTS. +// +// clang-format off +/* +Usage + + +wget https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2 +tar xf sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2 +rm sherpa-onnx-pocket-tts-int8-2026-01-26.tar.bz2 + +./pocket-tts-en-c-api + + */ +// clang-format on + +#include +#include +#include + +#include "sherpa-onnx/c-api/c-api.h" + +static int32_t ProgressCallback(const float *samples, int32_t num_samples, + float progress, void *arg) { + fprintf(stderr, "Progress: %.3f%%\n", progress * 100); + // return 1 to continue generating + // return 0 to stop generating + return 1; +} + +int32_t main(int32_t argc, char *argv[]) { + SherpaOnnxOfflineTtsConfig config; + memset(&config, 0, sizeof(config)); + config.model.pocket.lm_flow = + "./sherpa-onnx-pocket-tts-int8-2026-01-26/lm_flow.int8.onnx"; + config.model.pocket.lm_main = + "./sherpa-onnx-pocket-tts-int8-2026-01-26/lm_main.int8.onnx"; + config.model.pocket.encoder = + "./sherpa-onnx-pocket-tts-int8-2026-01-26/encoder.onnx"; + config.model.pocket.decoder = + "./sherpa-onnx-pocket-tts-int8-2026-01-26/decoder.int8.onnx"; + config.model.pocket.text_conditioner = + "./sherpa-onnx-pocket-tts-int8-2026-01-26/text_conditioner.onnx"; + config.model.pocket.vocab_json = + "./sherpa-onnx-pocket-tts-int8-2026-01-26/vocab.json"; + config.model.pocket.token_scores_json = + "./sherpa-onnx-pocket-tts-int8-2026-01-26/token_scores.json"; + + config.model.num_threads = 2; + + // If you don't want to see debug messages, please set it to 0 + config.model.debug = 1; + + const char *filename = "./generated-pocket-en.wav"; + const char *text = + "Today as always, men fall into two groups: slaves and free men. Whoever " + "does not have two-thirds of his day for himself, is a slave, whatever " + "he may be: a statesman, a businessman, an official, or a scholar. " + "Friends fell out often because life was changing so fast. The easiest " + "thing in the world was to lose touch with someone."; + + const SherpaOnnxOfflineTts *tts = SherpaOnnxCreateOfflineTts(&config); + if (!tts) { + fprintf(stderr, "Error create Offline TTS\n"); + return -1; + } + int32_t sid = 0; + float speed = 1.0; // larger -> faster in speech speed + GenerationConfig cfg = {0}; + const char *reference_audio_file = + "./sherpa-onnx-pocket-tts-int8-2026-01-26/test_wavs/bria.wav"; + const SherpaOnnxWave *wave = NULL; + wave = SherpaOnnxReadWave(reference_audio_file); + if (!wave) { + fprintf(stderr, "Failed to read %s\n", reference_audio_file); + SherpaOnnxDestroyOfflineTts(tts); + return -1; + } + cfg.reference_audio = wave->samples; + cfg.reference_audio_len = wave->num_samples; + cfg.reference_sample_rate = wave->sample_rate; + cfg.extra = "{\"max_reference_audio_len\": 10.0}"; + +#if 0 + // If you don't want to use a callback, then please enable this branch + const SherpaOnnxGeneratedAudio *audio = + SherpaOnnxOfflineTtsGenerateWithConfig(tts, text, &cfg, NULL, NULL); +#else + const SherpaOnnxGeneratedAudio *audio = + SherpaOnnxOfflineTtsGenerateWithConfig(tts, text, &cfg, ProgressCallback, + NULL); +#endif + + if (wave) SherpaOnnxFreeWave(wave); + + fprintf(stderr, "Input text is: %s\n", text); + fprintf(stderr, "Speaker ID is: %d\n", sid); + + if (audio) { + SherpaOnnxWriteWave(audio->samples, audio->n, audio->sample_rate, filename); + fprintf(stderr, "Saved to: %s\n", filename); + SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio); + } + + SherpaOnnxDestroyOfflineTts(tts); + + return 0; +} diff --git a/sherpa-onnx/c-api/c-api.cc b/sherpa-onnx/c-api/c-api.cc index 1958be85bd..72ad75c640 100644 --- a/sherpa-onnx/c-api/c-api.cc +++ b/sherpa-onnx/c-api/c-api.cc @@ -12,6 +12,8 @@ #include #include +#include "nlohmann/json.hpp" + #if __OHOS__ #include "rawfile/raw_file_manager.h" #endif @@ -1286,6 +1288,22 @@ static sherpa_onnx::OfflineTtsConfig GetOfflineTtsConfig( tts_config.model.zipvoice.guidance_scale = SHERPA_ONNX_OR(config->model.zipvoice.guidance_scale, 1.0f); + // pocket + tts_config.model.pocket.lm_flow = + SHERPA_ONNX_OR(config->model.pocket.lm_flow, ""); + tts_config.model.pocket.lm_main = + SHERPA_ONNX_OR(config->model.pocket.lm_main, ""); + tts_config.model.pocket.encoder = + SHERPA_ONNX_OR(config->model.pocket.encoder, ""); + tts_config.model.pocket.decoder = + SHERPA_ONNX_OR(config->model.pocket.decoder, ""); + tts_config.model.pocket.text_conditioner = + SHERPA_ONNX_OR(config->model.pocket.text_conditioner, ""); + tts_config.model.pocket.vocab_json = + SHERPA_ONNX_OR(config->model.pocket.vocab_json, ""); + tts_config.model.pocket.token_scores_json = + SHERPA_ONNX_OR(config->model.pocket.token_scores_json, ""); + tts_config.model.num_threads = SHERPA_ONNX_OR(config->model.num_threads, 1); tts_config.model.debug = config->model.debug; tts_config.model.provider = SHERPA_ONNX_OR(config->model.provider, "cpu"); @@ -1359,6 +1377,52 @@ static const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateInternal( return ans; } +static const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateInternal( + const SherpaOnnxOfflineTts *tts, const char *text, GenerationConfig *config, + std::function callback) { + sherpa_onnx::GenerationConfig cfg; + if (config->reference_audio_len > 0 && config->reference_audio) { + cfg.reference_audio.assign( + config->reference_audio, + config->reference_audio + config->reference_audio_len); + } + if (config->silence_scale > 0) cfg.silence_scale = config->silence_scale; + if (config->speed > 0) cfg.speed = config->speed; + cfg.sid = config->sid; + if (config->reference_sample_rate > 0) + cfg.reference_sample_rate = config->reference_sample_rate; + if (config->reference_text) + cfg.reference_text = config->reference_text; + if (config->num_steps > 0) cfg.num_steps = config->num_steps; + if (config->extra) { + try { + auto json = nlohmann::json::parse(config->extra); + for (auto &[k, v] : json.items()) { + cfg.extra.insert_or_assign(std::string(k), v.dump()); + } + } catch (const nlohmann::json::parse_error &e) { + SHERPA_ONNX_LOGE("Failed to parse extra JSON: %s", e.what()); + } + } + + sherpa_onnx::GeneratedAudio audio = tts->impl->Generate(text, cfg, callback); + + if (audio.samples.empty()) { + return nullptr; + } + + SherpaOnnxGeneratedAudio *ans = new SherpaOnnxGeneratedAudio; + + float *samples = new float[audio.samples.size()]; + std::copy(audio.samples.begin(), audio.samples.end(), samples); + + ans->samples = samples; + ans->n = audio.samples.size(); + ans->sample_rate = audio.sample_rate; + + return ans; +} + const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerate( const SherpaOnnxOfflineTts *tts, const char *text, int32_t sid, float speed) { @@ -1444,6 +1508,18 @@ const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateWithZipvoice( return ans; } +const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateWithConfig( + const SherpaOnnxOfflineTts *tts, const char *text, GenerationConfig *config, + SherpaOnnxGeneratedAudioProgressCallbackWithArg callback, void *arg) { + auto wrapper = [callback, arg](const float *samples, int32_t n, + float progress) { + if (!callback) return 1; + return callback(samples, n, progress, arg); + }; + + return SherpaOnnxOfflineTtsGenerateInternal(tts, text, config, std::move(wrapper)); +} + void SherpaOnnxDestroyOfflineTtsGeneratedAudio( const SherpaOnnxGeneratedAudio *p) { if (p) { @@ -1513,6 +1589,14 @@ const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateWithZipvoice( const SherpaOnnxOfflineTts *tts, const char *text, const char *prompt_text, const float *prompt_samples, int32_t n_prompt, int32_t prompt_sr, float speed, int32_t num_steps) { + SHERPA_ONNX_LOGE("TTS is not enabled. Please rebuild sherpa-onnx"); + return nullptr; +} + +const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateWithConfig( + const SherpaOnnxOfflineTts *tts, const char *text, GenerationConfig *config, + SherpaOnnxGeneratedAudioProgressCallbackWithArg callback, void *arg) { + SHERPA_ONNX_LOGE("TTS is not enabled. Please rebuild sherpa-onnx"); return nullptr; } @@ -1520,7 +1604,6 @@ void SherpaOnnxDestroyOfflineTtsGeneratedAudio( const SherpaOnnxGeneratedAudio *p) { SHERPA_ONNX_LOGE("TTS is not enabled. Please rebuild sherpa-onnx"); } - #endif // SHERPA_ONNX_ENABLE_TTS == 1 int32_t SherpaOnnxWriteWave(const float *samples, int32_t n, diff --git a/sherpa-onnx/c-api/c-api.h b/sherpa-onnx/c-api/c-api.h index 18c6d7984e..f50d038293 100644 --- a/sherpa-onnx/c-api/c-api.h +++ b/sherpa-onnx/c-api/c-api.h @@ -1098,6 +1098,16 @@ SHERPA_ONNX_API typedef struct SherpaOnnxOfflineTtsZipvoiceModelConfig { float guidance_scale; } SherpaOnnxOfflineTtsZipvoiceModelConfig; +SHERPA_ONNX_API typedef struct SherpaOnnxOfflineTtsPocketModelConfig { + const char *lm_flow; + const char *lm_main; + const char *encoder; + const char *decoder; + const char *text_conditioner; + const char *vocab_json; + const char *token_scores_json; +} SherpaOnnxOfflineTtsPocketModelConfig; + SHERPA_ONNX_API typedef struct SherpaOnnxOfflineTtsModelConfig { SherpaOnnxOfflineTtsVitsModelConfig vits; int32_t num_threads; @@ -1107,6 +1117,7 @@ SHERPA_ONNX_API typedef struct SherpaOnnxOfflineTtsModelConfig { SherpaOnnxOfflineTtsKokoroModelConfig kokoro; SherpaOnnxOfflineTtsKittenModelConfig kitten; SherpaOnnxOfflineTtsZipvoiceModelConfig zipvoice; + SherpaOnnxOfflineTtsPocketModelConfig pocket; } SherpaOnnxOfflineTtsModelConfig; SHERPA_ONNX_API typedef struct SherpaOnnxOfflineTtsConfig { @@ -1200,6 +1211,26 @@ SherpaOnnxOfflineTtsGenerateWithZipvoice(const SherpaOnnxOfflineTts *tts, int32_t n_prompt, int32_t prompt_sr, float speed, int32_t num_steps); +SHERPA_ONNX_API typedef struct GenerationConfig { + float silence_scale; + float speed; // used only by some models. + int32_t sid; // used only by models support multi-speakers + const float *reference_audio; // mono, [-1, 1] + int32_t reference_audio_len; // length in samples + int32_t reference_sample_rate; // sample rate of reference_audio + const char *reference_text; // not all models require this + int32_t num_steps; // number of steps in flow matching + const char *extra; // extra attrs in JSON object, model specific +} GenerationConfig; + +// Generate audio from the given text with config params. +// The user has to use SherpaOnnxDestroyOfflineTtsGeneratedAudio() to free the +// returned pointer to avoid memory leak. +SHERPA_ONNX_API const SherpaOnnxGeneratedAudio * +SherpaOnnxOfflineTtsGenerateWithConfig( + const SherpaOnnxOfflineTts *tts, const char *text, GenerationConfig *config, + SherpaOnnxGeneratedAudioProgressCallbackWithArg callback, void *arg); + SHERPA_ONNX_API void SherpaOnnxDestroyOfflineTtsGeneratedAudio( const SherpaOnnxGeneratedAudio *p); diff --git a/sherpa-onnx/c-api/cxx-api.h b/sherpa-onnx/c-api/cxx-api.h index e1981f0e9b..b8f8b988fc 100644 --- a/sherpa-onnx/c-api/cxx-api.h +++ b/sherpa-onnx/c-api/cxx-api.h @@ -459,12 +459,24 @@ struct OfflineTtsZipvoiceModelConfig { float guidance_scale = 1.0; }; +struct OfflineTtsPocketModelConfig { + std::string lm_flow; + std::string lm_main; + std::string encoder; + std::string decoder; + std::string text_conditioner; + + std::string vocab_json; + std::string token_scores_json; +}; + struct OfflineTtsModelConfig { OfflineTtsVitsModelConfig vits; OfflineTtsMatchaModelConfig matcha; OfflineTtsKokoroModelConfig kokoro; OfflineTtsKittenModelConfig kitten; OfflineTtsZipvoiceModelConfig zipvoice; + OfflineTtsPocketModelConfig pocket; int32_t num_threads = 1; bool debug = false;