codecamp

Ollama 语音(Speech)

先明确一件事:这一节讲的是文字转语音(TTS),不是语音转文字。 Ollama 的 Speech 能力目前负责"把文本读出来",让 chat 模型的回答能直接变成声音,而不是听懂你在说什么。

方向搞清楚了,用途其实很实在:给对话应用加上朗读、把长文转成通勤时能听的音频、让命令行脚本"说话"。

要求 Ollama v0.40.9 或更高;模型下载:ollama pull phonon。版本基线 v0.40.12,官方 docs.ollama.com/capabilities/speech。

一、语音模型与音色

当前语音模型是 phonon,基于 Gradium 的 31M 英文 checkpoint。内置英文音色有 Zoey、Easton、Freya 等;不指定音色时用 checkpoint 自带的 default。

查一下实际有哪些音色:

## 列出模型的能力(会显示 speech)与可用音色
ollama show phonon

音色名不区分大小写,也容忍重音符号——zoey、Zoey、ZÖEY 都能用。这一点比很多 TTS 服务宽松。

二、终端里直接朗读

ollama run 会直接通过系统扬声器播放:

## 朗读一段文本
ollama run phonon "Hello from Ollama."

## 指定音色
ollama run phonon --voice Zoey "Hi, this is Zoey."

更有意思的是管道。把别的程序的输出喂给它,就能朗读任何内容——包括 chat 模型的回答:

## 让聊天模型先回答,再把回答读出来
## --hidethinking 隐藏推理过程,只把正文传给 phonon
ollama run gemma4 --hidethinking "用两句话讲一个关于猫头鹰的趣事。" | ollama run phonon

管道是边到边读的:chat 模型吐出第一两句,语音就开始播放,不用等整段生成完。这让"AI 语音助手"的延迟感受大幅降低——用户听到的第一个字,出现在模型刚开始回答的时候。

用 ollama run phonon 不带文本会进入 >>> 提示符,每输入一行回车即朗读;Ctrl+C 停止播放,/bye 或 Ctrl+D 退出。

三、存成文件(服务器也能用)

把输出重定向,ollama run 就不播放,而是写出一个 WAV 文件:

## 重定向到文件:不播放,直接保存 WAV
ollama run phonon "Save this to a file." > speech.wav

这招在无声卡的服务器上同样有效——纯生成,不依赖音频设备。

如果模型跑在远程服务器,而你人在本地,用 OLLAMA_HOST 指向服务器,声音会从你运行命令的这台机器出来:

## 模型在 my-server 上跑,声音在本地播放
OLLAMA_HOST=my-server:11434 ollama run phonon "Hello from the server."

四、API:走 OpenAI 兼容的 /v1/audio/speech

集成用的端点是 /v1/audio/speech——注意它在 OpenAI 兼容层下,路径带 v1,而不是 /api/*。好消息是你可以直接复用 OpenAI 官方 SDK:

## REST 直调:把返回的音频流写进文件
curl http://localhost:11434/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{
    "model": "phonon",
    "input": "Hello from Ollama.",
    "voice": "default"
  }' --output speech.wav

Python 直接套用 openai 包,把 base_url 指向本地即可(api_key 随便填,Ollama 不校验):

## pip install openai
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

## 用流式响应逐块写盘,边生成边保存,不必等全部合成
with client.audio.speech.with_streaming_response.create(
    model="phonon",
    voice="default",
    input="Hello from Ollama.",
    response_format="wav",
) as response:
    response.stream_to_file("speech.wav")

JavaScript 版本:

// npm install openai
import fs from 'node:fs'
import OpenAI from 'openai'

const openai = new OpenAI({ baseURL: 'http://localhost:11434/v1', apiKey: 'ollama' })

const response = await openai.audio.speech.create({
  model: 'phonon',
  voice: 'default',
  input: 'Hello from Ollama.',
  response_format: 'wav',
})

fs.writeFileSync('speech.wav', Buffer.from(await response.arrayBuffer()))

响应是流式的,所以可以边生成边播放,不必先落盘再播:

## -N 关闭 curl 缓冲,直接管道给播放器,实现"边说边播"
curl -sN http://localhost:11434/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"model": "phonon", "input": "Playback starts while the rest is still being generated."}' \
  | ffplay -nodisp -autoexit -loglevel error -

五、音频格式只有两种

response_format 支持的取值很少,这点和商业 TTS 服务差别很大:

response_format 输出内容
wav(默认) WAV 文件:16 位、单声道、24 kHz。头部在长度未知时就已写好,因此文件头里的长度字段是最大值
pcm 裸 16 位小端采样,单声道、24 kHz,没有文件头

第二列关于 WAV 头的提示很重要。因为音频是流式生成的,服务端写入文件头时还不知道总长度,所以头里的 size 字段填的是上限值。绝大多数播放器能正常处理(它们靠实际数据长度判断),但如果你的代码要用文件头里的长度做进度计算或切片,就会算错——此时应改用 pcm 自己封头,或者按实际读到的字节数计算。

pcm 适合直接喂给音频设备或 Web Audio API;wav 适合存文件、发给别人。

六、六条限制,提前知道能省很多调试

单次输入上限 4096 字符。 ollama run 会自动在段落和句子边界切分超长文本,但你走 API 时得自己切。

数字和符号会被"拼读"。 价格、号码、符号、邮箱、URL 都会展开成口语,例如 $4.59 读作 "4 dollars and 59 cents"。这对朗读体验是好事,但如果你的文本里 URL 很多,会被逐字符念出来,建议朗读前先把链接替换成"链接"二字。

日期要写成文字形式。 时间与纯数字日期按原样读——10/23 会被念成"十斜杠二十三"之类。想自然朗读就写 October 23。

Emoji、项目符号、Markdown 标记会被跳过。 *、# 这类符号不会读出来,所以直接把 Markdown 原文丢进去朗读,听感上基本干净,但标题层级信息也丢了。

不支持的选项直接报错,不静默忽略。 其他格式、语速、instructions 等参数会返回 400。未知音色同样返回 400,并在错误信息里列出所有可用音色——这反过来是最好的"查音色"方式。

语音模型只会合成语音。 对 phonon 发 chat、completion 或 embedding 请求都会返回 400。别拿它当通用模型用。

七、一个实用的组合

把上面几节串起来,就是"把长文变成可听的播客":

## ① 让模型把文章改写成适合朗读的口语稿(去掉代码块、链接)
ollama run gemma4 --hidethinking "把下面这段技术文档改写成适合朗读的口语稿,不要代码和链接:$(cat article.md)" > script.txt

## ② 分段合成音频(每段不超过 4096 字符)
split -b 3000 script.txt part_
for f in part_*; do
  curl -sN http://localhost:11434/v1/audio/speech \
    -H "Content-Type: application/json" \
    -d "{\"model\": \"phonon\", \"input\": $(python -c "import json,sys;print(json.dumps(open('$f').read()))"), \"voice\": \"Zoey\", \"response_format\": \"wav\"}" \
    --output "$f.wav"
done

想给这个流程加上"回答问题前先判断该不该检索"的能力,看 3.4 Ollama 决策(Decision);想了解让模型自主调用外部工具,接下一篇 3.8 Ollama 工具调用(Tool calling)。

Ollama 嵌入向量(Embeddings)
Ollama 工具调用(Tool calling)
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

Ollama Cloud

Ollama CLI 命令行参考

Ollama Modelfile 参考

Ollama 上下文长度

Ollama 导入自定义模型

Ollama 常见问题

Ollama 硬件支持

Ollama 故障排查

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }