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

Unity中AudioClip.PCMReaderCallback写入audioBuffer后未触发求助

问题分析与修复方案

1. BinaryReader关闭底层MemoryStream导致后续读取失效

你在PcmReader中用using包裹BinaryReader,默认情况下BinaryReader会关闭底层的MemoryStream,第一次回调执行后,audioBuffer就被关闭,后续无法再写入或读取数据,回调自然无法正常触发。

修复:
创建BinaryReader时指定不关闭底层流,手动释放BinaryReader而非底层流:

private void PcmReader(float[] data)
{
    // ... 其他逻辑 ...
    var reader = new BinaryReader(audioBuffer, Encoding.Default, leaveOpen: true);
    try
    {
        Debug.Log("audioBuffer is read");
        for (int i = 0; i < data.Length; i++)
        {
            if (audioBuffer.Position < audioBuffer.Length)
            {
                short sample = reader.ReadInt16();
                data[i] = sample / 32768f;
            }
            else
            {
                data[i] = 0f;
            }
        }
    }
    finally
    {
        reader.Dispose(); // 释放BinaryReader,但保留底层流
    }
    // ... 其他逻辑 ...
}

2. AudioClip.Create参数错误

AudioClip.Create的第二个参数是总样本数,你当前设置的44100意味着音频仅1秒时长(44100样本 ÷ 44100采样率),如果WebSocket返回数据的速度慢于音频播放速度,可能还没收到数据,音频就已播放完毕,回调停止触发。

修复:
设置足够大的总样本数适配流式音频场景:

audioClip = AudioClip.Create("ElevenLabsTTS", int.MaxValue, 1, 44100, true, PcmReader);

3. 线程安全问题:WebSocket回调跨线程操作MemoryStream

WebSocket的OnMessage回调运行在非Unity主线程,直接写入MemoryStream会和主线程的PcmReader回调产生线程冲突,导致数据损坏或读取异常。

修复:
添加锁保护audioBuffer的读写操作:

private readonly object bufferLock = new object();

// PcmReader中读取时加锁
lock (bufferLock)
{
    if (audioBuffer == null || audioBuffer.Length == 0) return;
    // ... 读取buffer的逻辑 ...
}

// WebSocket回调中写入时加锁
websocket.OnMessage += (bytes) =>
{
    string message = Encoding.UTF8.GetString(bytes);
    var data = JsonUtility.FromJson<MessageData>(message);
    if (data.audio != null)
    {
        byte[] audioData = System.Convert.FromBase64String(data.audio);
        lock (bufferLock)
        {
            audioBuffer.Write(audioData);
        }
        Debug.Log("Added audio data");
    }
};

4. 优化MemoryStream重置逻辑

读取完所有数据后,不仅要清空长度,还要重置Position到0,避免后续写入位置错误:

lock (bufferLock)
{
    if (audioBuffer.Position >= audioBuffer.Length)
    {
        audioBuffer.SetLength(0);
        audioBuffer.Position = 0;
    }
}

完整修复后的关键代码片段

Start方法

private MemoryStream audioBuffer;
private AudioClip audioClip;
private AudioSource audioSource;
private readonly object bufferLock = new object();

async void Start()
{
    Debug.Log("Start");
    audioBuffer = new MemoryStream();
    audioClip = AudioClip.Create("ElevenLabsTTS", int.MaxValue, 1, 44100, true, PcmReader);

    await ConnectToWebSocket();
    audioSource = GetComponent<AudioSource>();
    audioSource.clip = audioClip;
    audioSource.Play();
    Debug.Log("Playing audio clip");
}

PcmReader回调

private void PcmReader(float[] data)
{
    lock (bufferLock)
    {
        if (audioBuffer == null || audioBuffer.Length == 0)
        {
            Array.Fill(data, 0f);
            return;
        }

        var reader = new BinaryReader(audioBuffer, Encoding.Default, leaveOpen: true);
        try
        {
            Debug.Log("audioBuffer is read");
            for (int i = 0; i < data.Length; i++)
            {
                if (audioBuffer.Position < audioBuffer.Length)
                {
                    short sample = reader.ReadInt16();
                    data[i] = sample / 32768f;
                }
                else
                {
                    data[i] = 0f;
                }
            }
        }
        finally
        {
            reader.Dispose();
        }

        if (audioBuffer.Position >= audioBuffer.Length)
        {
            audioBuffer.SetLength(0);
            audioBuffer.Position = 0;
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 03:37:16