Skip to content

Refactor TTS C API - #3127

Merged
csukuangfj merged 3 commits into
k2-fsa:masterfrom
csukuangfj:fix-tts-c-api
Feb 4, 2026
Merged

csukuangfj merged 3 commits into
k2-fsa:masterfrom
csukuangfj:fix-tts-c-api

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Feb 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • CI now builds and runs a PocketTTS example during the C API workflow and uploads generated WAV artifacts for inspection.
  • Bug Fixes

    • Improved input validation, null-checks, and error logging for TTS operations, reducing crashes and clarifying parse errors.
  • Chores

    • Tightened API const-safety and standardized default handling for TTS configuration values.

@csukuangfj
csukuangfj requested a review from Copilot February 4, 2026 02:47
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 4, 2026
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @csukuangfj, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on refactoring the C API for Text-to-Speech (TTS) functionality within sherpa-onnx. The changes primarily aim to enhance the API's robustness and maintainability by introducing comprehensive null pointer checks for input parameters, standardizing configuration handling with default values, and ensuring proper management of callback functions. These improvements contribute to a more stable and predictable TTS interface.

Highlights

  • Enhanced API Robustness: Implemented extensive null pointer checks for critical input parameters (e.g., tts, text, config, prompt_text, prompt_samples) across various TTS generation functions to prevent crashes and improve stability.
  • Improved Configuration Handling: Modified SherpaOnnxOfflineTtsGenerateInternal to accept const GenerationConfig* and utilize the SHERPA_ONNX_OR macro for setting default values for silence_scale, speed, reference_text, and num_steps, simplifying configuration logic.
  • Refined Callback Management: Updated TTS generation functions that accept callbacks to explicitly check if the callback is non-null before creating and passing a wrapper, ensuring correct behavior when no callback is provided.
  • Exported New Symbol: Added _SherpaOnnxOfflineTtsGenerateWithConfig to the exported symbols list, making the function accessible externally.
Changelog
  • sherpa-onnx/c-api/c-api.cc
    • Changed GenerationConfig *config to const GenerationConfig *config in SherpaOnnxOfflineTtsGenerateInternal and SherpaOnnxOfflineTtsGenerateWithConfig.
    • Added null checks for config->reference_audio and config->reference_audio_len in SherpaOnnxOfflineTtsGenerateInternal.
    • Replaced conditional assignments for silence_scale, speed, reference_text, and num_steps with SHERPA_ONNX_OR macro for default value assignment.
    • Improved error logging for JSON parsing failures in config->extra.
    • Introduced null checks for tts and text in SherpaOnnxOfflineTtsGenerate, SherpaOnnxOfflineTtsGenerateWithCallback, SherpaOnnxOfflineTtsGenerateWithProgressCallback, SherpaOnnxOfflineTtsGenerateWithProgressCallbackWithArg, and SherpaOnnxOfflineTtsGenerateWithCallbackWithArg.
    • Added null checks for prompt_text, prompt_samples in SherpaOnnxOfflineTtsGenerateWithZipvoice.
    • Added null checks for config in SherpaOnnxOfflineTtsGenerateWithConfig.
    • Modified callback functions to check for nullptr before creating and passing the std::function wrapper.
  • sherpa-onnx/c-api/c-api.h
    • Updated the declaration of SherpaOnnxOfflineTtsGenerateWithConfig to use const GenerationConfig *config.
  • sherpa-onnx/c-api/sherpa-onnx-symbols-c.exp
    • Added _SherpaOnnxOfflineTtsGenerateWithConfig to the list of exported symbols.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/c-api.yaml
Activity
  • No specific activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@coderabbitai

coderabbitai Bot commented Feb 4, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

The PR adds a GitHub Actions step to test PocketTTS, tightens null-checks and input validation across TTS C API entry points, makes GenerationConfig parameters const-correct in public/internal APIs, adjusts callback wrapper usage, and exports a new TTS symbol.

Changes

Cohort / File(s) Summary
CI: PocketTTS test step
.github/workflows/c-api.yaml
Added "Test PocketTTS" workflow step that builds the pocket-tts C example, downloads an int8 model, sets library paths, runs the binary, uploads generated WAVs as artifacts for each matrix OS.
TTS C API implementation
sherpa-onnx/c-api/c-api.cc
Changed GenerationConfig parameters to const; added null-checks for tts/text/config/prompt pointers; applied length checks for reference audio; conditional construction of callback wrapper lambdas (only when callbacks provided); standardized JSON parse error logging and defaulting behavior for generation config fields.
TTS C API header
sherpa-onnx/c-api/c-api.h
Updated public signature SherpaOnnxOfflineTtsGenerateWithConfig to accept a const GenerationConfig pointer.
Symbol exports
sherpa-onnx/c-api/sherpa-onnx-symbols-c.exp
Added exported symbol _SherpaOnnxOfflineTtsGenerateWithConfig to the C symbol map.

Sequence Diagram(s)

(omitted — changes are API-level and CI step; no multi-component sequential flow requiring diagram)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • k2-fsa/sherpa-onnx issue 3084 — Both modify the TTS generation API to use/forward a GenerationConfig-based interface and adjust Generate-related function signatures.

Possibly related PRs

  • k2-fsa/sherpa-onnx PR 3115 — Adjusts C API TTS generation paths and GenerationConfig usage; strongly related to the signature and wiring changes here.

Poem

🐰 With twitching nose and careful hop,

I guard each pointer till it stops,
Const and checks keep errors slim,
WAVs emerge at every whim,
A tiny rabbit claps — hip hop! 🎶

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Refactor TTS C API' accurately describes the main changes: improvements to null-checks, input validation, and API parameter modifications in the TTS C API implementation across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the TTS C API, improving its robustness by adding null pointer checks and fixing bugs related to null callbacks. The changes are generally positive, but I've identified a critical issue where an optional parameter has been made mandatory, which constitutes a breaking change. I've also pointed out a minor redundancy in the code. Please see my detailed comments.

Comment thread sherpa-onnx/c-api/c-api.cc Outdated
Comment on lines 1384 to 1393
if (!config->reference_audio) {
SHERPA_ONNX_LOGE("Reference audio is nullptr");
return nullptr;
}

if (config->reference_audio_len <= 0) {
SHERPA_ONNX_LOGE("Invalid reference audio len: %d",
config->reference_audio_len);
return nullptr;
}

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

This change makes reference_audio a mandatory parameter for SherpaOnnxOfflineTtsGenerateWithConfig. Previously, it was optional. This is a breaking change and will likely cause issues for users who are not using voice cloning features and thus not providing a reference audio. The checks for reference_audio and reference_audio_len should be conditional on reference_audio being provided.

A possible fix would be to change the logic to something like this:

  sherpa_onnx::GenerationConfig cfg;
  if (config->reference_audio) {
    if (config->reference_audio_len <= 0) {
      SHERPA_ONNX_LOGE("Invalid reference audio len: %d",
                       config->reference_audio_len);
      return nullptr;
    }
    cfg.reference_audio.assign(
        config->reference_audio,
        config->reference_audio + config->reference_audio_len);
  }
  // ... continue setting other cfg fields

Comment thread sherpa-onnx/c-api/c-api.cc Outdated
Comment on lines +1631 to +1632
if (!callback) return 1;
return callback(samples, n, progress, arg);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The if (!callback) check on line 1631 is redundant because this lambda is only created inside the if (callback) block on line 1628. You can simplify the lambda body.

      return callback(samples, n, progress, arg);

@csukuangfj
csukuangfj merged commit 68ef8e1 into k2-fsa:master Feb 4, 2026
9 of 21 checks passed
@csukuangfj
csukuangfj deleted the fix-tts-c-api branch February 4, 2026 02:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the TTS C API with a focus on improving parameter validation, error handling, and API consistency. The changes add a new SherpaOnnxOfflineTtsGenerateWithConfig function to the symbol exports and make the GenerationConfig parameter const-qualified across the API.

Changes:

  • Added comprehensive null pointer checks and error logging for TTS generation functions
  • Changed GenerationConfig* to const GenerationConfig* to indicate the config is not modified
  • Refactored callback wrapper logic to handle null callbacks explicitly
  • Updated config field handling to use SHERPA_ONNX_OR macro for default values
  • Added CI workflow test for PocketTTS with the new API

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
sherpa-onnx/c-api/sherpa-onnx-symbols-c.exp Added symbol export for SherpaOnnxOfflineTtsGenerateWithConfig
sherpa-onnx/c-api/c-api.h Changed GenerationConfig* to const GenerationConfig* parameter
sherpa-onnx/c-api/c-api.cc Added null checks, refactored validation logic, improved error messages, and restructured callback handling
.github/workflows/c-api.yaml Added CI test for PocketTTS example using the new API

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1397 to +1398
cfg.silence_scale = SHERPA_ONNX_OR(config->silence_scale, 0.2);
cfg.speed = SHERPA_ONNX_OR(config->speed, 1.0);

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Weakened validation for config fields: The old code used if (config->silence_scale > 0) to validate that the value is positive before using it. The new code uses SHERPA_ONNX_OR which treats any non-zero value as valid, including negative values. This means negative values like -0.5 would now be accepted and passed through, whereas they would have been rejected before (falling back to defaults).

If silence_scale and speed should only accept positive values, restore the > 0 check or add validation to reject negative values.

Suggested change
cfg.silence_scale = SHERPA_ONNX_OR(config->silence_scale, 0.2);
cfg.speed = SHERPA_ONNX_OR(config->speed, 1.0);
if (config->silence_scale > 0) {
cfg.silence_scale = config->silence_scale;
} else {
cfg.silence_scale = 0.2;
}
if (config->speed > 0) {
cfg.speed = config->speed;
} else {
cfg.speed = 1.0;
}

Copilot uses AI. Check for mistakes.

if (config->extra) {
try {
auto json = nlohmann::json::parse(config->extra);

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Weakened validation for num_steps: The old code used if (config->num_steps > 0) to validate that the value is positive before using it. The new code uses SHERPA_ONNX_OR which treats any non-zero value as valid, including negative values. This means negative values would now be accepted and passed through to the underlying implementation, whereas they would have been rejected before (falling back to the default of 5).

If num_steps should only accept positive values, restore the > 0 check or add validation to reject negative values.

Copilot uses AI. Check for mistakes.
Comment on lines +1385 to 1395
if (config->reference_audio) {
if (config->reference_audio_len <= 0) {
SHERPA_ONNX_LOGE("Invalid reference audio len: %d",
config->reference_audio_len);
return nullptr;
}

cfg.reference_audio.assign(
config->reference_audio,
config->reference_audio + config->reference_audio_len);
}

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

The reference audio validation logic has been changed from optional to mandatory. The old code checked if (config->reference_audio_len > 0 && config->reference_audio) before using reference audio, making it optional. The new code returns an error if reference_audio is nullptr, which breaks backward compatibility for callers who don't provide reference audio.

This is a breaking API change. Consider making reference_audio optional by checking both pointers first: if reference_audio is nullptr, skip the reference audio processing entirely instead of returning an error. This would maintain backward compatibility while adding the validation for cases where reference_audio is provided.

Copilot uses AI. Check for mistakes.
Comment on lines +1563 to +1565
if (!text) {
SHERPA_ONNX_LOGE("text is nullptr");
return nullptr;

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Inconsistent error logging: This function checks if tts is nullptr at line 1563 but doesn't log an error message before returning nullptr. This is inconsistent with the error logging pattern used in other similar functions (e.g., SherpaOnnxOfflineTtsGenerate at line 1444, SherpaOnnxOfflineTtsGenerateWithCallback at line 1460). Add a SHERPA_ONNX_LOGE statement before returning nullptr for consistency.

Copilot uses AI. Check for mistakes.
Comment on lines +1614 to +1616
if (!text) {
SHERPA_ONNX_LOGE("text is nullptr");
return nullptr;

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Inconsistent error logging: This function checks if tts is nullptr at line 1614 but doesn't log an error message before returning nullptr. This is inconsistent with the error logging pattern used in other similar functions (e.g., SherpaOnnxOfflineTtsGenerate at line 1444). Add a SHERPA_ONNX_LOGE statement before returning nullptr for consistency.

Copilot uses AI. Check for mistakes.
Comment on lines +1631 to +1632
std::move(wrapper));
} else {

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Redundant null check: The lambda checks if callback is nullptr at line 1631, but this code is already inside an if (callback) block that starts at line 1628. This check is unnecessary and can be removed. The lambda should simply call the callback directly.

Copilot uses AI. Check for mistakes.
std::string ptext_s = prompt_text;

std::vector<float> prompt_vec;
if (prompt_samples && n_prompt > 0) {

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Redundant null check: The condition if (prompt_samples && n_prompt > 0) checks prompt_samples again, but this pointer is already validated at line 1577 where the function returns nullptr if prompt_samples is null. The check for prompt_samples here is redundant and can be simplified to just if (n_prompt > 0).

Suggested change
if (prompt_samples && n_prompt > 0) {
if (n_prompt > 0) {

Copilot uses AI. Check for mistakes.

cfg.reference_text = SHERPA_ONNX_OR(config->reference_text, "");
cfg.num_steps = SHERPA_ONNX_OR(config->num_steps, 5);

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Missing validation for reference_sample_rate: The C++ implementation (offline-tts-pocket-impl.h:526) validates that reference_sample_rate must be greater than 0. However, the C API doesn't validate this before passing it to the C++ layer. If a caller doesn't set reference_sample_rate (leaving it as 0), the error will only be caught later in the C++ layer. Consider adding validation here: if (config->reference_sample_rate <= 0) { SHERPA_ONNX_LOGE("Invalid reference_sample_rate: %d", config->reference_sample_rate); return nullptr; }

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants