返回工具列表

语音识别与文字转语音实战:让AI能听能说的技术方案

📅 2026-02-10 🔄 2026-08-10 更新 ✍️ 轻语API开放平台 ⏱️ 阅读约 18 分钟

语音交互正在成为人机交互的核心入口。从智能客服到语音助手,从会议纪要到无障碍阅读,语音识别(ASR)和文字转语音(TTS)是构建语音能力的两大基石。本文将深入探讨ASR/TTS的技术原理、接口设计和工程实践,帮助开发者快速集成语音能力。

一、语音技术全景

1.1 两大核心能力

语音识别(ASR, Automatic Speech Recognition)
  音频 → 文字
  "你好世界" 的录音 → "你好世界"

文字转语音(TTS, Text-to-Speech)
  文字 → 音频
  "你好世界" → 一段自然流畅的语音音频

1.2 应用场景

场景 技术 说明
语音助手 ASR + TTS 听懂用户指令,语音播报结果
会议纪要 ASR 实时语音转文字,自动生成会议记录
有声读物 TTS 将文章、小说转为语音,解放双眼
智能客服 ASR + TTS 电话场景下的自动应答
无障碍服务 TTS 为视障用户朗读网页、文档内容
语音输入法 ASR 说话即打字,效率提升3倍
短视频配音 TTS 自动生成解说旁白,降低创作门槛
外语学习 TTS 标准发音示范,听力训练素材生成

二、语音识别(ASR)技术解析

2.1 技术原理

现代语音识别的主流技术路线已从传统HMM-GMM演进到端到端深度学习模型:

传统方案:
  音频 → 特征提取(MFCC) → 声学模型(HMM-GMM) → 语言模型(N-gram) → 文字

现代方案(端到端):
  音频 → 特征提取(Mel频谱) → 深度神经网络(Transformer/CTC) → 文字

关键技术指标:

指标 说明 行业水平
WER 词错误率,越低越好 中文普通话 < 5%
延迟 从说完到出结果的时间 实时场景 < 500ms
采样率 音频采样频率 16kHz(电话8kHz)
支持格式 可处理的音频格式 WAV, MP3, PCM, FLAC

2.2 音频格式基础

在调用语音识别之前,理解音频格式至关重要:

/**
 * 音频参数说明
 * - 采样率(Sample Rate):每秒采样次数,16000Hz最常用
 * - 位深(Bit Depth):每个采样点的精度,16bit标准
 * - 声道(Channel):单声道(Mono)用于语音识别
 * - 编码(Encoding):PCM原始数据 / MP3压缩 / WAV无损
 */
public class AudioConfig {
    public static final int SAMPLE_RATE = 16000;
    public static final int BIT_DEPTH = 16;
    public static final int CHANNELS = 1;  // Mono

    /**
     * 计算PCM音频时长(秒)
     */
    public static double getDuration(long byteSize) {
        // 时长 = 字节数 / (采样率 × 位深/8 × 声道数)
        return (double) byteSize / (SAMPLE_RATE * BIT_DEPTH / 8 * CHANNELS);
    }
}

2.3 接口设计

@RestController
@RequestMapping("/api")
public class SpeechController {

    @Autowired
    private SpeechRecognitionService asrService;

    /**
     * 语音识别:音频文件 → 文字
     * 支持格式:WAV, MP3, PCM, FLAC, M4A, OGG
     * 文件大小限制:20MB
     */
    @PostMapping("/speechRecognition")
    public Result recognize(
            @RequestParam("file") MultipartFile audioFile,
            @RequestParam(value = "language", defaultValue = "zh")
                String language) {

        // 1. 文件校验
        if (audioFile.isEmpty()) {
            return Result.error("音频文件不能为空");
        }

        long maxSize = 20 * 1024 * 1024; // 20MB
        if (audioFile.getSize() > maxSize) {
            return Result.error("文件大小不能超过20MB");
        }

        // 2. 格式校验
        String filename = audioFile.getOriginalFilename();
        if (!isValidAudioFormat(filename)) {
            return Result.error("不支持的音频格式,支持WAV/MP3/PCM/FLAC/M4A/OGG");
        }

        // 3. 执行识别
        RecognitionResult result = asrService.recognize(
            audioFile, language
        );

        return Result.success(result);
    }

    private boolean isValidAudioFormat(String filename) {
        if (filename == null) return false;
        String ext = filename.substring(filename.lastIndexOf(".") + 1)
                            .toLowerCase();
        return Set.of("wav", "mp3", "pcm", "flac", "m4a", "ogg")
                   .contains(ext);
    }
}

2.4 返回数据结构

{
    "code": 200,
    "data": {
        "text": "今天天气真不错,适合出去走走",
        "duration": 3.5,
        "language": "zh",
        "confidence": 0.96
    }
}

三、文字转语音(TTS)技术解析

3.1 技术原理

TTS经历了三代技术演进:

第一代:拼接合成
  文字 → 分词 → 从语音库中选取音素片段 → 拼接成语音
  特点:机械感强,像"机器人说话"

第二代:参数合成
  文字 → 语言学分析 → 声学参数预测 → 声码器合成
  特点:流畅度提升,但音质仍有差距

第三代:神经网络合成(当前主流)
  文字 → Encoder → Decoder(Tacotron/FastSpeech) → 声码器(HiFi-GAN) → 语音
  特点:接近真人发音,支持情感表达

3.2 关键参数

TTS的效果由多个参数共同决定:

参数 说明 取值范围
voice 发音人/音色 多种角色可选
speed 语速 0.5~2.0,1.0为正常
volume 音量 0~10,5为正常
pitch 音调 -10~10,0为正常
format 输出格式 MP3, WAV, PCM

3.3 接口设计

@RestController
@RequestMapping("/api")
public class TtsController {

    @Autowired
    private TextToSpeechService ttsService;

    /**
     * 文字转语音
     * 支持多种音色、语速、音量、音调调节
     */
    @PostMapping("/textToSpeech")
    public ResponseEntity<byte[]> synthesize(
            @RequestBody TtsRequest request) {

        // 1. 参数校验
        if (StringUtils.isBlank(request.getText())) {
            throw new IllegalArgumentException("文本内容不能为空");
        }

        if (request.getText().length() > 5000) {
            throw new IllegalArgumentException("文本长度不能超过5000字");
        }

        // 2. 设置默认值
        String voice = Optional.ofNullable(request.getVoice())
                               .orElse("zh-CN-XiaoxiaoNeural");
        float speed = Optional.ofNullable(request.getSpeed())
                              .orElse(1.0f);
        int volume = Optional.ofNullable(request.getVolume())
                             .orElse(5);

        // 3. 合成语音
        byte[] audioData = ttsService.synthesize(
            request.getText(), voice, speed, volume
        );

        // 4. 返回音频流
        return ResponseEntity.ok()
            .header("Content-Type", "audio/mpeg")
            .header("Content-Disposition",
                "attachment; filename=speech.mp3")
            .body(audioData);
    }
}

3.4 请求参数

{
    "text": "欢迎使用轻语API开放平台,我们为您提供稳定可靠的接口服务。",
    "voice": "zh-CN-XiaoxiaoNeural",
    "speed": 1.0,
    "volume": 5,
    "pitch": 0,
    "format": "mp3"
}

3.5 常见音色选择

不同场景适合不同的音色:

音色 风格 适用场景
晓晓 (Xiaoxiao) 温暖亲切的女声 通用播报、语音助手
云扬 (Yunyang) 稳重大气的男声 新闻播报、纪录片
晓墨 (Xiaomo) 活泼可爱的女声 儿童故事、趣味内容
云希 (Yunxi) 年轻阳光的男声 有声小说、教育场景

四、前端集成实践

4.1 语音识别 - 文件上传方式

/**
 * 上传音频文件进行语音识别
 */
async function recognizeSpeech(audioFile) {
    const formData = new FormData();
    formData.append('file', audioFile);
    formData.append('language', 'zh');

    const response = await fetch('/api/speechRecognition', {
        method: 'POST',
        body: formData
    });
    const data = await response.json();

    if (data.code === 200) {
        console.log('识别结果:', data.data.text);
        console.log('音频时长:', data.data.duration, '秒');
        console.log('置信度:', data.data.confidence);
    }
    return data;
}

// 绑定文件选择
document.getElementById('audioInput')
    .addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (file) recognizeSpeech(file);
    });

4.2 浏览器录音 + 识别

更好的用户体验是直接在浏览器中录音,然后发送识别:

class AudioRecorder {
    constructor() {
        this.mediaRecorder = null;
        this.chunks = [];
    }

    /**
     * 开始录音
     */
    async start() {
        const stream = await navigator.mediaDevices.getUserMedia({
            audio: {
                sampleRate: 16000,
                channelCount: 1,
                echoCancellation: true,
                noiseSuppression: true
            }
        });

        this.mediaRecorder = new MediaRecorder(stream, {
            mimeType: 'audio/webm;codecs=opus'
        });

        this.chunks = [];

        this.mediaRecorder.ondataavailable = (e) => {
            if (e.data.size > 0) {
                this.chunks.push(e.data);
            }
        };

        this.mediaRecorder.start();
        console.log('开始录音...');
    }

    /**
     * 停止录音并返回Blob
     */
    stop() {
        return new Promise((resolve) => {
            this.mediaRecorder.onstop = () => {
                const blob = new Blob(this.chunks, {
                    type: 'audio/webm'
                });
                // 停止所有音轨
                this.mediaRecorder.stream
                    .getTracks().forEach(t => t.stop());
                resolve(blob);
            };
            this.mediaRecorder.stop();
        });
    }
}

// 使用示例
const recorder = new AudioRecorder();

document.getElementById('recordBtn').addEventListener('click',
    async () => {
        await recorder.start();
    }
);

document.getElementById('stopBtn').addEventListener('click',
    async () => {
        const audioBlob = await recorder.stop();
        // 将录音发送到识别接口
        const file = new File([audioBlob], 'recording.webm', {
            type: 'audio/webm'
        });
        const result = await recognizeSpeech(file);
        document.getElementById('resultText').textContent =
            result.data.text;
    }
);

4.3 文字转语音 - 在线播放

/**
 * 文字转语音并播放
 */
async function speakText(text, options = {}) {
    const requestBody = {
        text: text,
        voice: options.voice || 'zh-CN-XiaoxiaoNeural',
        speed: options.speed || 1.0,
        volume: options.volume || 5,
        format: 'mp3'
    };

    const response = await fetch('/api/textToSpeech', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(requestBody)
    });

    if (!response.ok) {
        throw new Error('语音合成失败');
    }

    // 将响应转为Blob并播放
    const audioBlob = await response.blob();
    const audioUrl = URL.createObjectURL(audioBlob);
    const audio = new Audio(audioUrl);

    audio.onended = () => {
        URL.revokeObjectURL(audioUrl); // 释放内存
    };

    await audio.play();
    return audio;
}

// 使用示例
document.getElementById('speakBtn').addEventListener('click',
    () => {
        const text = document.getElementById('textInput').value;
        speakText(text, { voice: 'zh-CN-YunxiNeural', speed: 1.1 });
    }
);

4.4 语音播放器组件

提供更完善的播放控制体验:

class SpeechPlayer {
    constructor(containerEl) {
        this.container = containerEl;
        this.audio = null;
        this.isPlaying = false;
    }

    /**
     * 合成并加载音频(不自动播放)
     */
    async load(text, options) {
        const response = await fetch('/api/textToSpeech', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ text, ...options })
        });

        const blob = await response.blob();
        const url = URL.createObjectURL(blob);

        if (this.audio) {
            this.audio.pause();
            URL.revokeObjectURL(this.audio.src);
        }

        this.audio = new Audio(url);
        this.bindEvents();
    }

    bindEvents() {
        this.audio.addEventListener('timeupdate', () => {
            const progress = this.audio.currentTime /
                             this.audio.duration * 100;
            this.updateProgress(progress);
        });

        this.audio.addEventListener('ended', () => {
            this.isPlaying = false;
            this.updateUI();
        });
    }

    toggle() {
        if (!this.audio) return;
        if (this.isPlaying) {
            this.audio.pause();
        } else {
            this.audio.play();
        }
        this.isPlaying = !this.isPlaying;
        this.updateUI();
    }

    /**
     * 调节语速(重新合成)
     */
    async changeSpeed(text, speed) {
        await this.load(text, { speed });
        this.toggle();
    }
}

五、工程实践要点

5.1 音频预处理

实际项目中,原始音频质量参差不齐,预处理可以显著提升识别准确率:

public class AudioPreprocessor {

    /**
     * 音频预处理流程
     * 1. 格式转换:统一转为16kHz/16bit/Mono的WAV
     * 2. 降噪:去除背景噪声
     * 3. 静音检测:去除首尾静音段
     * 4. 音量归一化:统一音量水平
     */
    public byte[] preprocess(InputStream audioInput, String format) {
        // 转为标准WAV格式
        AudioFormat targetFormat = new AudioFormat(
            16000, // 采样率
            16,    // 位深
            1,     // 单声道
            true,  // signed
            false  // little-endian
        );

        // 使用FFmpeg进行格式转换和降噪
        ProcessBuilder pb = new ProcessBuilder(
            "ffmpeg", "-i", "pipe:0",
            "-ar", "16000",        // 采样率
            "-ac", "1",            // 单声道
            "-sample_fmt", "s16",  // 16bit
            "-af", "silenceremove=1:0:-40dB," +  // 去首尾静音
                   "highpass=f=80," +             // 高通滤波
                   "lowpass=f=8000," +            // 低通滤波
                   "volume=1.5",                  // 音量增益
            "-f", "wav", "pipe:1"
        );

        // 执行转换...
        return executeProcess(pb, audioInput);
    }
}

5.2 长音频分片处理

对于会议录音等长音频,需要分片处理:

public class LongAudioProcessor {

    private static final int CHUNK_DURATION = 60; // 每片60秒
    private static final int OVERLAP = 2;         // 2秒重叠

    /**
     * 长音频分片识别
     * 策略:将长音频按固定时长切片,相邻片段保留重叠区间,
     * 识别后根据重叠部分去重拼接
     */
    public String recognizeLong(InputStream audioInput) {
        List<byte[]> chunks = splitAudio(audioInput,
            CHUNK_DURATION, OVERLAP);

        StringBuilder fullText = new StringBuilder();

        for (int i = 0; i < chunks.size(); i++) {
            String chunkText = asrService.recognize(chunks.get(i));

            if (i > 0 && chunkText.length() > 0) {
                // 去除重叠部分的重复文字
                chunkText = removeOverlap(
                    fullText.toString(), chunkText
                );
            }

            fullText.append(chunkText);
        }

        return fullText.toString();
    }

    /**
     * 去除重叠部分
     * 思路:找到前一段末尾和当前段开头的最长公共子串
     */
    private String removeOverlap(String previous, String current) {
        int checkLength = Math.min(20, previous.length());
        String tail = previous.substring(previous.length() - checkLength);

        for (int len = checkLength; len >= 2; len--) {
            String suffix = tail.substring(checkLength - len);
            if (current.startsWith(suffix)) {
                return current.substring(suffix.length());
            }
        }
        return current;
    }
}

5.3 TTS长文本分段合成

TTS接口通常有文本长度限制,长文本需要分段合成后拼接:

public class LongTextSynthesizer {

    private static final int MAX_CHARS = 500; // 每段最大字符数

    /**
     * 长文本分段合成
     * 按句号、感叹号、问号等自然断句点切分
     */
    public byte[] synthesizeLong(String text, String voice) {
        List<String> segments = splitByNaturalBreaks(text, MAX_CHARS);
        List<byte[]> audioSegments = new ArrayList<>();

        for (String segment : segments) {
            byte[] audio = ttsService.synthesize(segment, voice);
            audioSegments.add(audio);
        }

        // 拼接所有音频片段
        return concatenateAudio(audioSegments);
    }

    /**
     * 按自然断句点切分文本
     */
    private List<String> splitByNaturalBreaks(String text, int maxLen) {
        List<String> segments = new ArrayList<>();
        StringBuilder current = new StringBuilder();

        // 按句子分割
        String[] sentences = text.split("(?<=[。!?;\\.!?;])");

        for (String sentence : sentences) {
            if (current.length() + sentence.length() > maxLen
                    && current.length() > 0) {
                segments.add(current.toString().trim());
                current = new StringBuilder();
            }
            current.append(sentence);
        }

        if (current.length() > 0) {
            segments.add(current.toString().trim());
        }

        return segments;
    }
}

5.4 缓存策略

TTS合成是计算密集型操作,相同文本+相同参数的结果应当缓存:

@Service
public class TtsCacheService {

    private final Cache<String, byte[]> cache = Caffeine.newBuilder()
        .maximumWeight(500 * 1024 * 1024)  // 最大500MB
        .weigher((String k, byte[] v) -> v.length)
        .expireAfterAccess(Duration.ofHours(6))
        .build();

    /**
     * 生成缓存Key
     * 基于文本内容+语音参数生成唯一标识
     */
    private String buildKey(String text, String voice,
                            float speed, int volume) {
        String raw = text + "|" + voice + "|" + speed + "|" + volume;
        return DigestUtils.md5Hex(raw);
    }

    public byte[] getOrSynthesize(String text, String voice,
                                   float speed, int volume) {
        String key = buildKey(text, voice, speed, volume);
        return cache.get(key, k ->
            ttsService.synthesize(text, voice, speed, volume)
        );
    }
}

六、常见问题与调优

6.1 语音识别准确率提升

问题 原因 解决方案
识别结果有错别字 同音字混淆 结合语言模型做纠错
背景噪声干扰 录音环境嘈杂 音频预处理降噪
方言识别差 模型主要训练普通话 指定方言参数或使用专用模型
专业术语识别错误 词表中缺少专业词汇 添加自定义热词
长段落断句混乱 连续语音缺少停顿 结合标点预测模型

6.2 TTS音质优化

1. 文本预处理
   - 数字转读法:"2024年" → "二零二四年"
   - 英文处理:"API" → 逐字母读 或 整词读
   - 特殊符号:"¥100" → "一百元"

2. SSML标记增强(如果支持)
   <speak>
     欢迎使用
     <break time="500ms"/>
     轻语API
     <emphasis level="strong">开放平台</emphasis>
   </speak>

3. 参数微调
   - 语速:新闻播报1.0~1.1,故事朗读0.9~1.0
   - 音调:根据内容情绪适当调整
   - 停顿:段落间自动插入适当停顿

七、在线体验

想快速体验语音识别和文字转语音的效果?轻语API开放平台 提供了开箱即用的语音接口服务:

按次计费低至0.001元/次,注册即送免费额度,适合个人开发者和中小企业快速集成语音能力。

八、总结

语音识别和文字转语音是AI语音交互的两大支柱,工程落地时需要关注:

  1. 音频预处理:格式转换、降噪、静音去除,直接影响识别准确率
  2. 长内容分片:无论ASR还是TTS,长内容都需要分片处理再拼接
  3. 缓存复用:TTS结果的缓存可以显著降低计算开销和响应延迟
  4. 参数调优:音色选择、语速音调调节、热词配置,让效果更贴合业务场景
  5. 错误处理:网络超时、格式不支持、内容过长等异常情况的兜底

掌握这些要点,就能为你的应用快速接入"能听能说"的语音能力。


本文基于实际项目经验总结,欢迎交流讨论。