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

C#获取Action<> Lambda参数值与Unity+Photon Voice音频转WAV问题

问题1:从Lambda调用的Action<>中获取参数值并存入数组

针对这个问题,核心是利用C#的闭包特性,或者封装一个参数追踪类来实现,分两种常见场景给你说明:

场景1:在Lambda内部直接存储参数

如果只是想在Lambda执行时把参数存入数组/集合,直接在Lambda代码块里操作外部定义的存储容器就行:

// 定义一个集合来存储参数(用List比数组更灵活,后续可转数组)
List<object> parameterStorage = new List<object>();

// 声明带参数的Action,比如Action<int, string>
Action<int, string> myAction = (num, str) => {
    // 将参数添加到存储容器
    parameterStorage.Add(num);
    parameterStorage.Add(str);
    // 这里可以保留你原本的Action逻辑
};

// 调用Action
myAction(123, "Hello Lambda");

// 后续需要数组的话直接转换
object[] paramArray = parameterStorage.ToArray();

场景2:封装类统一追踪所有调用的参数

如果需要更规范地管理多次调用的参数,比如按调用顺序存储完整参数组,可以封装一个追踪类:

public class ActionParamTracker<T1, T2>
{
    // 存储每次调用的参数组
    public List<Tuple<T1, T2>> RecordedParams { get; } = new List<Tuple<T1, T2>>();

    // 返回带追踪逻辑的Action
    public Action<T1, T2> TrackedAction => (p1, p2) => {
        RecordedParams.Add(Tuple.Create(p1, p2));
        // 这里可以添加你原本要执行的业务逻辑
    };
}

// 使用示例
var tracker = new ActionParamTracker<int, string>();
Action<int, string> trackedAction = tracker.TrackedAction;

// 多次调用Action
trackedAction(456, "First Call");
trackedAction(789, "Second Call");

// 遍历查看所有记录的参数
foreach (var paramTuple in tracker.RecordedParams)
{
    Debug.Log($"Param1: {paramTuple.Item1}, Param2: {paramTuple.Item2}");
}

问题2:Unity + Photon Voice 保存音频流为WAV文件

你已经能拿到音频帧的float数据了,接下来只需要三步:收集帧数据、转成WAV兼容的PCM格式、生成WAV文件头并写入磁盘,一步步拆解给你:

1. 先持续收集音频帧数据

首先定义类级别的容器来存储收到的音频帧,同时记录Photon Voice的音频参数(采样率、通道数要和Photon Voice的设置一致,一般语音是单声道、48kHz采样率):

// 存储所有收到的音频帧
private List<float> collectedAudioFrames = new List<float>();
// 音频参数,需和Photon Voice实际配置匹配
private int audioSampleRate = 48000;
private int audioChannels = 1;

然后在你已有的OnAudioFrameAction回调里收集帧:

PhotonVoiceNetwork.Client.OnAudioFrameAction += (playerId, voiceId, frame) => {
    // 将当前帧的float数组添加到容器
    collectedAudioFrames.AddRange(frame);
    Debug.LogFormat("***** {0} {1} {2}", playerId, voiceId, frame[0]);
};

2. 将float音频数据转成WAV兼容的16位PCM字节

WAV文件的PCM数据通常是16位有符号整数,需要把Photon Voice返回的float(范围[-1,1])缩放到short(范围[-32768,32767])并转成字节:

private byte[] ConvertFloatsTo16BitPCM(List<float> floatSamples)
{
    byte[] pcmBytes = new byte[floatSamples.Count * 2]; // 每个short占2字节
    int byteIndex = 0;
    foreach (float sample in floatSamples)
    {
        // 把float缩放到short范围
        short pcmSample = (short)(sample * short.MaxValue);
        // WAV使用小端字节序,拆分short为两个字节
        pcmBytes[byteIndex++] = (byte)(pcmSample & 0xff);
        pcmBytes[byteIndex++] = (byte)((pcmSample >> 8) & 0xff);
    }
    return pcmBytes;
}

3. 生成WAV文件头并写入完整文件

WAV有固定的文件头格式,我们构造头信息后,和PCM数据一起写入文件:

private void SaveAudioToWav(string savePath, byte[] pcmData, int sampleRate, int channels)
{
    using (FileStream fileStream = new FileStream(savePath, FileMode.Create))
    using (BinaryWriter writer = new BinaryWriter(fileStream))
    {
        // 写入RIFF块
        writer.Write(new char[] { 'R', 'I', 'F', 'F' });
        int totalFileSize = 36 + pcmData.Length;
        writer.Write(totalFileSize);
        writer.Write(new char[] { 'W', 'A', 'V', 'E' });

        // 写入fmt块(PCM格式)
        writer.Write(new char[] { 'f', 'm', 't', ' ' });
        writer.Write(16); // PCM格式块大小
        writer.Write((short)1); // PCM格式代码
        writer.Write((short)channels);
        writer.Write(sampleRate);
        writer.Write(sampleRate * channels * 2); // 字节率:采样率*通道数*位深度/8(16位=2字节)
        writer.Write((short)(channels * 2)); // 块对齐:通道数*位深度/8
        writer.Write((short)16); // 位深度

        // 写入data块
        writer.Write(new char[] { 'd', 'a', 't', 'a' });
        writer.Write(pcmData.Length);
        writer.Write(pcmData);
    }
}

4. 触发保存操作

比如加一个UI按钮,点击时执行保存:

public void TriggerSaveAudio()
{
    if (collectedAudioFrames.Count == 0)
    {
        Debug.LogWarning("没有可保存的音频数据!");
        return;
    }

    // 转换为16位PCM字节
    byte[] pcmData = ConvertFloatsTo16BitPCM(collectedAudioFrames);
    // 保存路径用Unity的持久化路径,避免权限问题
    string savePath = Path.Combine(Application.persistentDataPath, $"player_audio_{DateTime.Now.Ticks}.wav");
    
    SaveAudioToWav(savePath, pcmData, audioSampleRate, audioChannels);
    Debug.Log($"音频已保存到:{savePath}");

    // 可选:清空容器,准备下一次录制
    collectedAudioFrames.Clear();
}

注意事项

  • 务必确认Photon Voice的实际采样率,比如Opus解码后的采样率可能是48kHz或16kHz,参数不匹配会导致播放声音变调;
  • 如果需要区分多个玩家的音频,可以用Dictionary<int, List<float>>按playerId分别存储;
  • Unity中文件操作建议放到协程里异步执行,避免阻塞主线程影响帧率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:54:32