← Code Recipes
Speech & Audio

Google Cloud Speech Free code examples in C#, Python, Java and JavaScript

Google's text-to-speech and speech-to-text, with many Indian languages.

C# Python Java JavaScript

WHAT THIS PAGE COVERS

Hindi and other Indic voices, and how the language codes actually work.

Model hi-IN-Chirp3-HD-Charon
Package google-cloud-texttospeech
Checked

Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.

Install

Google ships a first-party .NET client for this one — unlike Gemini, where the .NET path is the REST API. Auth is still Application Default Credentials.

dotnet add package Google.Cloud.TextToSpeech.V1

setx GOOGLE_APPLICATION_CREDENTIALS "C:\path\to\service-account.json"
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
Speaking Hindi

Create() picks up Application Default Credentials. AudioContent is a ByteString, so WriteTo on a file stream avoids copying the whole clip into a byte array first.

using Google.Cloud.TextToSpeech.V1;

var client = await TextToSpeechClient.CreateAsync();

var response = await client.SynthesizeSpeechAsync(
    new SynthesisInput { Text = "नमस्ते, यह हिंदी में एक परीक्षण है।" },
    new VoiceSelectionParams
    {
        LanguageCode = "hi-IN",
        Name = "hi-IN-Chirp3-HD-Charon",
    },
    new AudioConfig { AudioEncoding = AudioEncoding.Mp3 });

await using var output = File.Create("hindi.mp3");
response.AudioContent.WriteTo(output);
Listing the voices that exist today

Availability differs by language and changes without notice. One ListVoices call beats a constant that was true when someone wrote it.

var voices = await client.ListVoicesAsync(new ListVoicesRequest
{
    LanguageCode = "hi-IN",
});

foreach (var voice in voices.Voices)
{
    if (!voice.Name.Contains("Chirp3-HD", StringComparison.Ordinal))
    {
        continue;
    }

    Console.WriteLine($"{voice.Name}  {voice.SsmlGender}  {voice.NaturalSampleRateHertz}Hz");
}
One character across four languages

The voice name is a locale prefix plus a character. Keeping the character fixed and changing only the prefix is how a multilingual product keeps one identity.

var greetings = new Dictionary<string, string>
{
    ["hi-IN"] = "नमस्ते, आपका स्वागत है।",
    ["en-IN"] = "Hello, and welcome.",
    ["bn-IN"] = "নমস্কার, স্বাগতম।",
    ["ta-IN"] = "வணக்கம், வரவேற்கிறோம்.",
};

foreach (var (locale, text) in greetings)
{
    var spoken = await client.SynthesizeSpeechAsync(
        new SynthesisInput { Text = text },
        new VoiceSelectionParams
        {
            LanguageCode = locale,
            Name = $"{locale}-Chirp3-HD-Charon",
        },
        new AudioConfig { AudioEncoding = AudioEncoding.Mp3 });

    await using var file = File.Create($"greeting-{locale}.mp3");
    spoken.AudioContent.WriteTo(file);
}
Install

No API key. Google Cloud uses Application Default Credentials — either a service-account file, or `gcloud auth application-default login` on a laptop.

pip install google-cloud-texttospeech

# A service account, for a server:
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"

# Or, on a developer machine:
gcloud auth application-default login
Speaking Hindi

The locale goes in TWO places and they must agree: language_code on the voice, and the locale prefix of the name. A mismatch is accepted and produces an accent nobody asked for.

from google.cloud import texttospeech

client = texttospeech.TextToSpeechClient()

response = client.synthesize_speech(
    input=texttospeech.SynthesisInput(
        text="नमस्ते, यह हिंदी में एक परीक्षण है।"
    ),
    voice=texttospeech.VoiceSelectionParams(
        language_code="hi-IN",
        name="hi-IN-Chirp3-HD-Charon",
    ),
    audio_config=texttospeech.AudioConfig(
        audio_encoding=texttospeech.AudioEncoding.MP3
    ),
)

with open("hindi.mp3", "wb") as handle:
    handle.write(response.audio_content)
Finding out which voices actually exist

DO NOT HARDCODE A VOICE LIST. Availability changes per language and per region, and list_voices is the only answer that is true on the day you run it — the same reasoning as the Bedrock recipe's model discovery step.

voices = client.list_voices(language_code="hi-IN")

for voice in voices.voices:
    # Chirp 3 HD voices are the ones with Chirp3-HD in the name; the
    # rest are Standard, WaveNet, Neural2 or Studio, which have
    # different capabilities and different pricing.
    if "Chirp3-HD" in voice.name:
        print(voice.name, voice.ssml_gender.name, voice.natural_sample_rate_hertz)
The same character, a different language

Charon is Charon in every locale that has it. Swapping the prefix moves language without changing how the voice sounds, which is what keeps a multilingual product from sounding like four different products.

GREETINGS = {
    "hi-IN": "नमस्ते, आपका स्वागत है।",
    "en-IN": "Hello, and welcome.",
    "bn-IN": "নমস্কার, স্বাগতম।",
    "ta-IN": "வணக்கம், வரவேற்கிறோம்.",
}

for locale, text in GREETINGS.items():
    response = client.synthesize_speech(
        input=texttospeech.SynthesisInput(text=text),
        voice=texttospeech.VoiceSelectionParams(
            language_code=locale,
            name=f"{locale}-Chirp3-HD-Charon",
        ),
        audio_config=texttospeech.AudioConfig(
            audio_encoding=texttospeech.AudioEncoding.MP3
        ),
    )

    with open(f"greeting-{locale}.mp3", "wb") as handle:
        handle.write(response.audio_content)
Install

The BOM keeps the client and its gRPC transitive dependencies on versions that agree with each other — skipping it is the usual cause of a NoSuchMethodError at startup.

# Gradle
implementation(platform("com.google.cloud:libraries-bom:26.66.0"))
implementation("com.google.cloud:google-cloud-texttospeech")

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
Speaking Hindi

The client is AutoCloseable and expensive to build — one per process, not one per request. ByteString gives you the bytes without another copy.

import com.google.cloud.texttospeech.v1.*;
import com.google.protobuf.ByteString;

import java.io.FileOutputStream;
import java.io.OutputStream;

public class SpeakHindi {
    public static void main(String[] args) throws Exception {
        try (TextToSpeechClient client = TextToSpeechClient.create()) {
            SynthesisInput input = SynthesisInput.newBuilder()
                .setText("नमस्ते, यह हिंदी में एक परीक्षण है।")
                .build();

            VoiceSelectionParams voice = VoiceSelectionParams.newBuilder()
                .setLanguageCode("hi-IN")
                .setName("hi-IN-Chirp3-HD-Charon")
                .build();

            AudioConfig audio = AudioConfig.newBuilder()
                .setAudioEncoding(AudioEncoding.MP3)
                .build();

            SynthesizeSpeechResponse response =
                client.synthesizeSpeech(input, voice, audio);

            ByteString content = response.getAudioContent();
            try (OutputStream out = new FileOutputStream("hindi.mp3")) {
                content.writeTo(out);
            }
        }
    }
}
Listing the voices that exist today

Which Chirp 3 HD voices exist for a given Indic locale changes. Ask, rather than shipping a constant that was accurate once.

try (TextToSpeechClient client = TextToSpeechClient.create()) {
    ListVoicesResponse voices = client.listVoices("hi-IN");

    for (Voice voice : voices.getVoicesList()) {
        if (!voice.getName().contains("Chirp3-HD")) {
            continue;
        }

        System.out.printf("%s  %s  %dHz%n",
            voice.getName(),
            voice.getSsmlGender(),
            voice.getNaturalSampleRateHertz());
    }
}
The locale prefix is the only thing that changes

Same character, four languages. This is the whole trick to sounding like one product in a country with twenty-two official languages.

import java.util.Map;

Map<String, String> greetings = Map.of(
    "hi-IN", "नमस्ते, आपका स्वागत है।",
    "en-IN", "Hello, and welcome.",
    "bn-IN", "নমস্কার, স্বাগতম।",
    "ta-IN", "வணக்கம், வரவேற்கிறோம்.");

try (TextToSpeechClient client = TextToSpeechClient.create()) {
    for (Map.Entry<String, String> entry : greetings.entrySet()) {
        String locale = entry.getKey();

        SynthesizeSpeechResponse response = client.synthesizeSpeech(
            SynthesisInput.newBuilder().setText(entry.getValue()).build(),
            VoiceSelectionParams.newBuilder()
                .setLanguageCode(locale)
                .setName(locale + "-Chirp3-HD-Charon")
                .build(),
            AudioConfig.newBuilder().setAudioEncoding(AudioEncoding.MP3).build());

        try (OutputStream out = new FileOutputStream("greeting-" + locale + ".mp3")) {
            response.getAudioContent().writeTo(out);
        }
    }
}
Install

The npm package is scoped. Credentials come from the environment, so nothing about the key lives in code.

npm install @google-cloud/text-to-speech

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
Speaking Hindi

synthesizeSpeech returns an array — the response is the first element. Destructuring it, as below, is the idiom in every Google Cloud client and it catches people out once.

import { writeFile } from "node:fs/promises";
import textToSpeech from "@google-cloud/text-to-speech";

const client = new textToSpeech.TextToSpeechClient();

const [response] = await client.synthesizeSpeech({
  input: { text: "नमस्ते, यह हिंदी में एक परीक्षण है।" },
  voice: {
    languageCode: "hi-IN",
    name: "hi-IN-Chirp3-HD-Charon",
  },
  audioConfig: { audioEncoding: "MP3" },
});

await writeFile("hindi.mp3", response.audioContent, "binary");
Listing the voices that exist today

A hardcoded voice list goes stale the first time Google adds a language. listVoices is one call and it is correct by construction.

const [available] = await client.listVoices({ languageCode: "hi-IN" });

for (const voice of available.voices) {
  if (!voice.name.includes("Chirp3-HD")) continue;

  console.log(voice.name, voice.ssmlGender, voice.naturalSampleRateHertz);
}
What Chirp 3 HD will quietly ignore

speakingRate, pitch and SSML input are accepted and have NO EFFECT on a Chirp 3 HD voice. No error is raised. If you need them, that is a reason to pick a Neural2 or Studio voice instead.

// This runs, returns audio, and ignores both knobs.
const [ignored] = await client.synthesizeSpeech({
  input: { text: "This is read at the default rate and pitch." },
  voice: { languageCode: "hi-IN", name: "hi-IN-Chirp3-HD-Charon" },
  audioConfig: {
    audioEncoding: "MP3",
    speakingRate: 1.4,   // ignored by Chirp 3 HD
    pitch: -4.0,         // ignored by Chirp 3 HD
  },
});

// A Neural2 voice honours both, at the cost of a less natural read.
const [controlled] = await client.synthesizeSpeech({
  input: { text: "This one is genuinely faster." },
  voice: { languageCode: "hi-IN", name: "hi-IN-Neural2-A" },
  audioConfig: { audioEncoding: "MP3", speakingRate: 1.4 },
});

QUESTIONS

Which languages are the Google Cloud Speech examples in?

C#, Python, Java and JavaScript. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.

Which package do I install for Google Cloud Speech?

The samples use google-cloud-texttospeech. The install step on each language tab gives the exact command.

Which Google Cloud Speech model do these examples use?

hi-IN-Chirp3-HD-Charon, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.

Where is the official Google Cloud Speech documentation?

https://cloud.google.com/text-to-speech/docs — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.

ALSO IN SPEECH & AUDIO

The authority

Official Google Cloud Speech documentation

Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.

Read the docs ↗

https://cloud.google.com/text-to-speech/docs

New drops, every week

Free tools & Claude templates, landing regularly.

We keep shipping new free tools, code recipes and Claude Code templates. Follow @freeyantra on Instagram or join the newsletter so you never miss one.

Follow @freeyantra

or get them by email

Double opt-in. Unsubscribe any time. Privacy