You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过WebSocket向TwiML双向通话发送媒体消息无声音问题

问题:通过WebSocket发送Google TTS音频到Twilio通话无声音

我正在开发一个脚本,流程为:用Google Cloud Text-to-Speech生成LINEAR16编码的音频,通过WaveFile库转成mulaw/8000格式并转为base64,最后发送到WebSocket服务器,但通话中发送媒体消息时完全没有声音(连杂音都没有)。

我执行了以下步骤:


1. Node.js WebSocket服务器代码

const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });

const path = require("path");

wss.on("connection", function connection(ws) {
    console.log("New Connection Initiated");

    ws.on("message", function incoming(message) {
        let msg;
        try {
            msg = JSON.parse(message);
        } catch (e) {
            console.error('Error parsing message:', e);
            return;
        }
        switch (msg.event) {
            case "connected":
                console.log(`A new call has connected.`);
                break;
            case "start":
                console.log(`Starting Media Stream ${msg.streamSid}`);
                break;
            case "media":
                // Write Media Packets to the recognize stream
                if (msg.test == 1)
                    console.log(msg)
                break;
            case "stop":
                console.log(`Call Has Ended`);
                break;
        }
    });
});

console.log("Listening on Port 8080");

2. 用TwiML API发起通话(PHP代码)

$sid = "SID_HERE";
$token = "TOKEN_HERE";

$client = new Twilio\Rest\Client($sid, $token);

$xml ='
<Response>
    <Say>This is your Voice Assistant speaking!</Say>
    <Connect>
        <Stream url="wss://MY_WEBSOCKET_SERVER_HERE">
            <Parameter name="aCutomParameter" value="aCustomValue that was set in TwiML" />
        </Stream>
    </Connect>
</Response>';


$call = $client->account->calls->create(
    '+TARGET_NUMBER_HERE',
    '+TWILIO_NUMBER_HERE',
    [
        'twiml' => $xml
    ]
);

3. 生成并发送TTS音频的Node.js代码

const textToSpeech = require('@google-cloud/text-to-speech');
const WebSocket = require('ws');
const ttsClient = new textToSpeech.TextToSpeechClient();
const wavefile = require('wavefile');
const fs = require('fs');

const path = require("path");

require("dotenv").config();

async function generateTtsAudio(text) {
    const request = {
        input: { text: text },
        voice: { languageCode: 'en-US', ssmlGender: 'NEUTRAL' },
        audioConfig: { audioEncoding: 'LINEAR16', sampleRateHertz: 8000 },
    };

    const [response] = await ttsClient.synthesizeSpeech(request);
    return response.audioContent;
}

async function sendTtsToWebSocket(text, wsUrl, streamSid) {
    const audioContent = await generateTtsAudio(text);

    const wav = new wavefile.WaveFile(audioContent);
        wav.toBitDepth('8');
        wav.toSampleRate(8000);
        av.toMuLaw();

    const base64Audio = Buffer.from(wav.data.samples).toString('base64');
    console.log(base64Audio);

    const ws = new WebSocket(wsUrl);

    ws.on('open', function open() {
        const message = {
            event: 'media',
            streamSid: streamSid,
            media: { payload: base64Audio }
        };

        const markMessage = {
            event: 'mark',
            streamSid: streamSid,
            "mark": {
                name: "testmark"
            }
        };

        ws.send(JSON.stringify(message));
        ws.send(JSON.stringify(markMessage));
    });

    ws.on('error', function error(error) {
        console.error('WebSocket Error:', error);
    });
}

const wsUrl = 'wss://MY_WEBSOCKET_SERVER_HERE';
const streamSid = 'STREAM_SID_HERE'; // 从通话启动时获取
const text = 'Hello, this is a TTS test message.';

sendTtsToWebSocket(text, wsUrl, streamSid)
    .catch(console.error);

问题排查与修复方案

1. 代码拼写错误

音频转换部分存在变量名拼写错误:av.toMuLaw()应为wav.toMuLaw(),这会导致mulaw转换步骤失败,直接跳过音频格式处理。

2. 音频数据处理错误

WaveFile的toMuLaw()转换后,音频数据是二进制流而非样本数组,无需从wav.data.samples提取,直接使用wav.toBuffer()获取转换后的二进制数据:

const wav = new wavefile.WaveFile(audioContent);
wav.toBitDepth('8');
wav.toSampleRate(8000);
wav.toMuLaw();

// 正确获取mulaw编码后的二进制数据并转base64
const base64Audio = wav.toBuffer().toString('base64');

3. WebSocket连接复用问题

Twilio Media Stream要求复用Twilio主动发起的WebSocket连接,而非新建连接。修改WebSocket服务器代码保存活跃连接,并通过该连接发送媒体消息:

// WebSocket服务器中添加活跃连接存储
let activeCallConnection = null;

wss.on("connection", function connection(ws) {
    console.log("New Connection Initiated");
    activeCallConnection = ws;
    
    ws.on("close", () => {
        activeCallConnection = null;
    });

    // ... 原有消息处理逻辑
});

// 导出连接实例供TTS模块使用
module.exports = { activeCallConnection };

修改TTS发送函数,复用已有连接:

const { activeCallConnection } = require('./你的WebSocket服务器文件名');

async function sendTtsToWebSocket(text, streamSid) {
    if (!activeCallConnection) {
        console.error("无活跃通话连接");
        return;
    }

    // ... 原有音频生成与转换逻辑

    const message = {
        event: 'media',
        streamSid: streamSid,
        media: { payload: base64Audio }
    };

    activeCallConnection.send(JSON.stringify(message));
}

4. 明确音频声道数

在Google TTS请求中指定单声道(Twilio要求单声道音频):

const request = {
    input: { text: text },
    voice: { languageCode: 'en-US', ssmlGender: 'NEUTRAL' },
    audioConfig: { 
        audioEncoding: 'LINEAR16', 
        sampleRateHertz: 8000,
        audioChannelCount: 1 // 指定单声道
    },
};

内容的提问来源于stack exchange,提问作者Michael Naidis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 11:34:55