PocketTTS 100M — MediaPipe & LiteRT

Self-contained .litertlm packages of Kyutai PocketTTS (100M-parameter continuous FlowLM + Mimi neural audio codec, 24 kHz mono PCM output) quantized to INT8 for on-device speech synthesis with the MediaPipe Text-to-Speech (TextToSpeech) Task and LiteRT across Android, iOS, macOS, Python, Web, and C++.

Each .litertlm bundle packages the INT8 LiteRT subgraphs (text conditioner, 6-layer stateful autoregressive FlowLM backbone, 2-step Euler LSD flow-matching network, and stateful Mimi streaming neural codec decoder), the SentencePiece tokenizer, and pre-encoded voice conditioning embeddings into a single memory-mapped file.

Available Model Bundles

File Size Languages Bundled Voices Recommended Use Case
pocket_tts_en_int8.litertlm ⭐ 156 MB English (en-US) All 8 English voices (alba, marius, javert, jean, fantine, cosette, eponine, azelma) Recommended for English applications on mobile, desktop, and web.
pocket_tts_int8.litertlm 725 MB All 7 languages (en-US, fr-FR, de-DE, es-ES, it-IT, pt-BR, nl-NL) All 14 voices across 7 languages Single-file multilingual deployment with sub-millisecond cross-lingual voice switching.
pocket_tts_fr_int8.litertlm 122 MB French (fr-FR) estelle (default), alba Standalone French bundle or companion file alongside pocket_tts_en_int8.litertlm.
pocket_tts_de_int8.litertlm 121 MB German (de-DE) juergen (default), alba Standalone German bundle or companion file alongside pocket_tts_en_int8.litertlm.
pocket_tts_es_int8.litertlm 121 MB Spanish (es-ES) lola (default), alba Standalone Spanish bundle or companion file alongside pocket_tts_en_int8.litertlm.
pocket_tts_it_int8.litertlm 120 MB Italian (it-IT) giovanni (default), alba Standalone Italian bundle or companion file alongside pocket_tts_en_int8.litertlm.
pocket_tts_pt_int8.litertlm 121 MB Portuguese (pt-BR) rafael (default), alba Standalone Portuguese bundle or companion file alongside pocket_tts_en_int8.litertlm.
pocket_tts_nl_int8.litertlm 120 MB Dutch (nl-NL) daan (default), alba Standalone Dutch bundle or companion file alongside pocket_tts_en_int8.litertlm.

Modular Companion Language Loading: If you load pocket_tts_en_int8.litertlm and place any per-language pocket_tts_<lang>_int8.litertlm file in the same directory, MediaPipe TextToSpeech automatically discovers and loads the companion language model on demand when you request that language or voice—allowing you to ship only the languages your users download.

Supported Languages & Voices

Language BCP 47 Tag Bundled Voice IDs
English en-US alba (default), marius, javert, jean, fantine, cosette, eponine, azelma
French fr-FR estelle (default for fr)
German de-DE juergen (default for de)
Spanish es-ES lola (default for es)
Portuguese pt-BR rafael (default for pt)
Italian it-IT giovanni (default for it)
Dutch nl-NL daan (default for nl)

You can query all voices packaged inside the loaded .litertlm bundle at runtime using listVoices() / list_voices(). When switching voices via SynthesisOptions, setting voiceId automatically infers the matching language if locale is omitted.


MediaPipe Text-to-Speech Task Capabilities

When loaded with the MediaPipe TextToSpeech Task, PocketTTS supports:

  • Single-file .litertlm deployment: Produces normalized 24 kHz mono 32-bit floating-point (float32) PCM audio in [-1.0, 1.0] with zero external phonemizer or Python dependencies.
  • Low-latency streaming speech synthesis (synthesizeStream / synthesize_stream): Streams AudioChunk buffers as latent frames are generated and decoded by the stateful Mimi codec, using adaptive first-chunk scheduling (1 frame on step 0, 2 frames on steps 1–4, 4 frames thereafter) for ~45–60 ms Time-To-First-Audio (TTFA).
  • Real-time LLM-to-TTS token streaming (openStream / start_stream): Push incremental text deltas (pushText / push_text) directly from an on-device LLM stream and call finishInput / finish_input when done. Speech synthesis begins immediately once the first clause or word-boundary threshold arrives.
  • Word-level timestamp alignment (wordTimestamps / word_timestamps): Both unary AudioResult and streaming AudioChunk results include pause-aware WordTimestamp entries (word, startChar, endChar, startSec, endSec) mapped directly to character offsets in the input string for real-time read-along text highlighting.
  • Sub-millisecond voice & language switching: Switch voices or languages per utterance using SynthesisOptions in < 1 ms without reloading model weights.
  • Built-in text normalization & smart clause chunking: Automatically verbalizes numbers, decimals, ordinals (1st, 2nd), years, currencies ($, €, £), percentages, and times, and segments multi-sentence paragraphs at clause boundaries while guarding common abbreviations (Dr., Mr., U.S., e.g.), decimal numbers (3.14), and numbered lists.
  • Speaking rate control & lock-free barge-in cancellation: Adjust speakingRate ((0.1, 4.0], default 1.0) via pitch-preserving WSOLA time-scale modification, and abort in-flight synthesis within milliseconds via cancel() or native Kotlin Coroutine / Swift Task cancellation.

Download

pip install -U huggingface_hub

# English-only bundle (156 MB, 8 voices)
hf download litert-community/pocket-tts pocket_tts_en_int8.litertlm --local-dir .

# Multilingual 7-language bundle (725 MB, 14 voices)
hf download litert-community/pocket-tts pocket_tts_int8.litertlm --local-dir .

Usage with MediaPipe Text-to-Speech Task

1. Android (Kotlin)

See the MediaPipe Text-to-Speech Android Guide and Android Sample App for a complete project setup.

Add the Gradle dependency

Add the MediaPipe Audio Tasks dependency to your module's build.gradle.kts (minSdk = 26, arm64-v8a):

dependencies {
    implementation("com.google.mediapipe:tasks-audio:latest.release")
}

Initialize the task and list voices

import com.google.mediapipe.tasks.audio.texttospeech.SynthesisOptions
import com.google.mediapipe.tasks.audio.texttospeech.TextToSpeech
import com.google.mediapipe.tasks.audio.texttospeech.TextToSpeechOptions
import com.google.mediapipe.tasks.core.BaseOptions
import com.google.mediapipe.tasks.core.Delegate

val options = TextToSpeechOptions(
    baseOptions = BaseOptions.builder()
        .setModelAssetPath("/data/user/0/com.example.app/files/pocket_tts_en_int8.litertlm")
        .setDelegate(Delegate.CPU)
        .build(),
    defaultVoiceId = "alba",
    speakingRate = 1.0f,
    cacheDirectory = context.cacheDir.absolutePath,
)

val tts = TextToSpeech.create(options)

// Enumerate bundled voices at runtime:
for (voice in tts.listVoices()) {
    println("${voice.voiceId} (${voice.displayName}) - locale=${voice.locale}, ${voice.sampleRateHz} Hz")
}

Unary synthesis & word-level timestamps

// Non-blocking coroutine synthesis (or use tts.synthesize(...) on a background thread):
val result = tts.synthesizeAsync(
    "Hello from PocketTTS running on-device with MediaPipe Text to Speech!",
    SynthesisOptions(voiceId = "marius"),
)
// result.samples: FloatArray (24 kHz mono float32 PCM in [-1.0, 1.0])
// result.sampleRateHz: 24000
// result.durationSeconds: Float

// Word-level timestamps for synchronized read-along UI highlighting:
for (wt in result.wordTimestamps) {
    println("Word '${wt.word}' chars=[${wt.startChar}, ${wt.endChar}) time=${wt.startSec}s..${wt.endSec}s")
}

Streaming synthesis & incremental LLM-to-TTS streaming

// 1. Streaming a multi-sentence prompt clause-by-clause / chunk-by-chunk:
tts.synthesizeStream(
    "Streaming synthesis plays the first audio chunk in ~50 ms. Subsequent chunks arrive while audio plays.",
    SynthesisOptions(voiceId = "alba"),
).collect { chunk ->
    streamingAudioTrack.write(chunk.samples, 0, chunk.samples.size, AudioTrack.WRITE_BLOCKING)
    // chunk.wordTimestamps provides word timing offsets for the current chunk
}

// 2. Incremental token-by-token streaming from an on-device LLM:
tts.openStream(options = SynthesisOptions(voiceId = "alba")).use { stream ->
    stream.pushText("Hello from an ")
    stream.pushText("on-device LLM! ")
    stream.pushText("Audio starts streaming before generation finishes.")
    stream.finishInput()

    while (true) {
        val chunk = stream.readNext() ?: break
        streamingAudioTrack.write(chunk.samples, 0, chunk.samples.size, AudioTrack.WRITE_BLOCKING)
    }
}

// Release native resources when done:
tts.close()

2. iOS & macOS (Swift)

See the MediaPipe Text-to-Speech iOS Guide and iOS Sample App.

Add the Swift Package

In Xcode, select File > Add Package Dependencies..., enter https://github.com/google-ai-edge/mediapipe, and add the MediaPipeTasksAudio library to your target.

Initialize and synthesize speech

import Foundation
import MediaPipeTasksAudio

let modelPath = Bundle.main.path(forResource: "pocket_tts_en_int8", ofType: "litertlm")!
let cacheDir = FileManager.default.temporaryDirectory
    .appendingPathComponent("mediapipe_tts_cache").path

let options = TextToSpeechOptions(
    baseOptions: TTSBaseOptions(
        modelAssetPath: modelPath,
        delegate: .cpu
    ),
    defaultVoiceId: "alba",
    speakingRate: 1.0,
    cacheDirectory: cacheDir
)

let tts = try createTextToSpeech(options: options)

// Enumerate bundled voices:
for voice in try tts.listVoices() {
    print("\(voice.voiceId) - locale=\(voice.locale), \(voice.sampleRateHz) Hz")
}

// 1. Async unary synthesis with word-level timestamps:
let result = try await tts.synthesizeAsync(
    "Hello from PocketTTS on Apple platforms!",
    options: SynthesisOptions(voiceId: "alba")
)
for wt in result.wordTimestamps {
    print("\(wt.word) [\(wt.startChar)..<\(wt.endChar)]: \(wt.startSec)s - \(wt.endSec)s")
}

// 2. Low-latency streaming synthesis:
for try await chunk in tts.synthesizeStream(
    "Streaming synthesis emits 24 kHz PCM chunks as soon as each latent group decodes.",
    options: SynthesisOptions(voiceId: "cosette")
) {
    // Schedule chunk.samples (24 kHz mono Float) onto AVAudioPlayerNode
}

// 3. Incremental LLM-to-TTS streaming:
let stream = try tts.openStream(options: SynthesisOptions(voiceId: "alba"))
defer { stream.close() }

try stream.pushText("Streaming tokens from ")
try stream.pushText("an on-device language model.")
try stream.finishInput()

while let chunk = try stream.readNext() {
    // Play chunk.samples & highlight chunk.wordTimestamps
}

3. Python

import soundfile as sf
from mediapipe.tasks.python.audio import text_to_speech
from mediapipe.tasks.python.core import base_options

options = text_to_speech.TextToSpeechOptions(
    base_options=base_options.BaseOptions(
        model_asset_path="pocket_tts_en_int8.litertlm"
    ),
    default_voice_id="alba",
    speaking_rate=1.0,
)

with text_to_speech.TextToSpeech.create_from_options(options) as tts:
    # List bundled voices
    for voice in tts.list_voices():
        print(f"{voice.voice_id}: locale={voice.locale}, {voice.sample_rate_hz} Hz")

    # 1. Unary synthesis + word-level timestamps
    result = tts.synthesize(
        "Hello from PocketTTS running on-device with MediaPipe in Python!",
        voice_id="marius",
    )
    sf.write("output.wav", result.samples, result.sample_rate_hz)
    for wt in result.word_timestamps:
        print(f"{wt.word} [{wt.start_char}:{wt.end_char}] {wt.start_sec:.2f}s - {wt.end_sec:.2f}s")

    # 2. Streaming synthesis
    for chunk in tts.synthesize_stream(
        "First sentence streams right away. Second sentence follows seamlessly.",
        voice_id="alba",
    ):
        print(f"Chunk {chunk.chunk_index}: {len(chunk.samples)} samples (final={chunk.is_final})")

    # 3. Incremental LLM token streaming
    with tts.start_stream(voice_id="alba") as stream:
        stream.push_text("Streaming tokens ")
        stream.push_text("directly into PocketTTS.")
        stream.finish_input()
        for chunk in stream:
            pass  # Play chunk.samples in real time

4. Web (JavaScript / TypeScript)

import { FilesetResolver, TextToSpeech } from "@mediapipe/tasks-audio";

const wasmFileset = await FilesetResolver.forAudioTasks(
  "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-audio@latest/wasm"
);

const tts = await TextToSpeech.createFromOptions(wasmFileset, {
  baseOptions: {
    modelAssetPath: "pocket_tts_en_int8.litertlm",
    delegate: "CPU",
  },
  defaultVoiceId: "alba",
  speakingRate: 1.0,
});

// List bundled voices
const voices = await tts.listVoices();
console.log("Available voices:", voices);

// 1. Unary synthesis
const result = await tts.synthesize(
  "Hello from PocketTTS in the browser via MediaPipe WebAssembly!",
  { voiceId: "alba" }
);
// result.samples: Float32Array, result.sampleRateHz: 24000, result.wordTimestamps

// 2. Streaming synthesis
const stream = await tts.synthesizeStream(
  "Streaming speech directly to the Web Audio API.",
  { voiceId: "marius" }
);
for await (const chunk of stream) {
  // Schedule chunk.samples (Float32Array at chunk.sampleRateHz) on AudioContext
}

// 3. Incremental LLM token streaming (finishInput = false)
const llmStream = await tts.synthesizeStream("", { voiceId: "alba" }, /* finishInput= */ false);
await llmStream.pushText("Streaming tokens ");
await llmStream.pushText("from WebGPU LLM into PocketTTS.");
await llmStream.finishInput();
for await (const chunk of llmStream) {
  // Play chunk.samples and highlight chunk.wordTimestamps
}

tts.close();

5. C++

#include "mediapipe/tasks/cc/audio/text_to_speech/text_to_speech.h"

using ::mediapipe::tasks::audio::text_to_speech::SynthesisOptions;
using ::mediapipe::tasks::audio::text_to_speech::TextToSpeech;
using ::mediapipe::tasks::audio::text_to_speech::TextToSpeechOptions;

auto options = std::make_unique<TextToSpeechOptions>();
options->base_options.model_asset_path = "pocket_tts_en_int8.litertlm";
options->default_voice_id = "alba";
options->speaking_rate = 1.0f;

ABSL_ASSIGN_OR_RETURN(auto tts, TextToSpeech::Create(std::move(options)));

// Unary synthesis:
SynthesisOptions synth_opts{.voice_id = "alba"};
ABSL_ASSIGN_OR_RETURN(auto result, tts->Synthesize("Hello from C++!", synth_opts));
// result.samples (std::vector<float>), result.sample_rate_hz (24000), result.word_timestamps

// Streaming synthesis:
ABSL_ASSIGN_OR_RETURN(auto stream, tts->SynthesizeStream("Streaming from C++.", synth_opts));
while (true) {
  ABSL_ASSIGN_OR_RETURN(auto chunk, stream->ReadNext());
  if (!chunk.has_value()) break;
  // Consume chunk->samples and chunk->word_timestamps
}

Configuration Options Reference

TextToSpeechOptions

Option (Kotlin / Swift / JS) Option (Python / C++) Description Default
baseOptions.modelAssetPath base_options.model_asset_path Path (or URL on Web) to the .litertlm model bundle. Required
baseOptions.delegate base_options.delegate Execution delegate (CPU recommended for PocketTTS XNNPACK inference). CPU
defaultVoiceId default_voice_id Default voice identifier (e.g. "alba", "marius", "estelle"). "alba"
defaultLocale default_locale Default BCP 47 language tag (e.g. "en-US", "fr-FR"). Automatically inferred from voice ID when omitted. Inferred from voice
speakingRate speaking_rate Speaking speed multiplier in (0.1, 4.0]. 1.0
cacheDirectory cache_directory Writable directory path to persist XNNPACK weight caches across runs. ""

SynthesisOptions (per-utterance override)

Option (Kotlin / Swift / JS) Option (Python / C++) Description Default
voiceId voice_id Overrides the voice profile for a single synthesis or streaming call in < 1 ms. Task default
locale locale Optional BCP 47 locale override (e.g. "fr-FR", "de-DE"). Inferred from voiceId if unset. Inferred from voiceId

License & Attribution

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for litert-community/pocket-tts

Quantized
(1)
this model

Collections including litert-community/pocket-tts