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 @@ -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)
Expand Down
113 changes: 113 additions & 0 deletions c-api-examples/pocket-tts-en-c-api.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>

#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;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
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);
Comment on lines +67 to +110

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The main function has some potential issues with error handling and resource management:

  1. The return values of SherpaOnnxCreateOfflineTts (line 67) and SherpaOnnxOfflineTtsGenerateWithConfigAndProgressCallback (lines 94-96) are not checked for NULL. This could lead to a segmentation fault if either function fails and returns NULL.
  2. exit(-1) is used (line 77) when SherpaOnnxTtsGenerationConfigCreate fails. It's better to return from main and ensure all previously allocated resources are properly released to prevent memory leaks.
  const SherpaOnnxOfflineTts *tts = SherpaOnnxCreateOfflineTts(&config);
  if (!tts) {
    fprintf(stderr, "Failed to create TTS engine.\n");
    return -1;
  }
  // mapping of sid to voice name
  // 0->af, 1->af_bella, 2->af_nicole, 3->af_sarah, 4->af_sky, 5->am_adam
  // 6->am_michael, 7->bf_emma, 8->bf_isabella, 9->bm_george, 10->bm_lewis
  int32_t sid = 0;
  float speed = 1.0;  // larger -> faster in speech speed
  SherpaOnnxTtsGenerationConfig *gen_config =
      SherpaOnnxTtsGenerationConfigCreate();
  if (!gen_config) {
    fprintf(stderr, "Error create config\n");
    SherpaOnnxDestroyOfflineTts(tts);
    return -1;
  }
  SherpaOnnxTtsGenerationConfigSetInt(gen_config, "sid", sid);
  SherpaOnnxTtsGenerationConfigSetFloat(gen_config, "speed", speed);
  SherpaOnnxTtsGenerationConfigSetStr(
      gen_config, "reference_audio_file",
      "./sherpa-onnx-pocket-tts-2026-01-26/test_wavs/bria.wav");
  SherpaOnnxTtsGenerationConfigSetFloat(gen_config, "max_reference_audio_len", 10.0);
  const char *config_str = SherpaOnnxTtsGenerationConfigToString(gen_config);
  fprintf(stderr, "generation config: %s\n", config_str);
  free((void *)config_str);

#if 0
  // If you don't want to use a callback, then please enable this branch
  const SherpaOnnxGeneratedAudio *audio =
      SherpaOnnxOfflineTtsGenerateWithConfig(tts, text, gen_config);
#else
  const SherpaOnnxGeneratedAudio *audio =
      SherpaOnnxOfflineTtsGenerateWithConfigAndProgressCallback(
          tts, text, gen_config, ProgressCallback);
#endif

  SherpaOnnxTtsGenerationConfigDestroy(&gen_config);

  if (!audio) {
    fprintf(stderr, "Failed to generate audio.\n");
    SherpaOnnxDestroyOfflineTts(tts);
    return -1;
  }

  SherpaOnnxWriteWave(audio->samples, audio->n, audio->sample_rate, filename);

  SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio);
  SherpaOnnxDestroyOfflineTts(tts);


return 0;
}
85 changes: 84 additions & 1 deletion sherpa-onnx/c-api/c-api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <utility>
#include <vector>

#include "nlohmann/json.hpp"

#if __OHOS__
#include "rawfile/raw_file_manager.h"
#endif
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -1359,6 +1377,52 @@ static const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateInternal(
return ans;
}

static const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateInternal(
const SherpaOnnxOfflineTts *tts, const char *text, GenerationConfig *config,
std::function<int32_t(const float *, int32_t, float)> 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());
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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;
}
Comment on lines +1380 to +1424

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add null check for config parameter to prevent crashes.

The function dereferences config on line 1382 without first checking if it's null. If a caller passes a null config, this will cause a crash.

🛡️ Proposed fix
 static const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerateInternal(
     const SherpaOnnxOfflineTts *tts, const char *text, GenerationConfig *config,
     std::function<int32_t(const float *, int32_t, float)> callback) {
+  if (!config) {
+    SHERPA_ONNX_LOGE("config is null");
+    return nullptr;
+  }
+
   sherpa_onnx::GenerationConfig *cfg;
   if (config->pvt) {
🤖 Prompt for AI Agents
In `@sherpa-onnx/c-api/c-api.cc` around lines 1378 - 1420,
SherpaOnnxOfflineTtsGenerateInternal currently dereferences the parameter config
without checking for null; add a null-check at the start of
SherpaOnnxOfflineTtsGenerateInternal and handle it (either return nullptr
immediately or create a local sherpa_onnx::GenerationConfig to use as a default)
to avoid crashes; update logic that uses config->pvt and later deletes cfg to
account for the chosen handling so you don't dereference config when it's null
and ensure proper memory ownership for the local GenerationConfig.


const SherpaOnnxGeneratedAudio *SherpaOnnxOfflineTtsGenerate(
const SherpaOnnxOfflineTts *tts, const char *text, int32_t sid,
float speed) {
Expand Down Expand Up @@ -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));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void SherpaOnnxDestroyOfflineTtsGeneratedAudio(
const SherpaOnnxGeneratedAudio *p) {
if (p) {
Expand Down Expand Up @@ -1513,14 +1589,21 @@ 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;
}

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,
Expand Down
31 changes: 31 additions & 0 deletions sherpa-onnx/c-api/c-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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);

Expand Down
12 changes: 12 additions & 0 deletions sherpa-onnx/c-api/cxx-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down