Я пытаюсь реализовать голосовой чат в Godot с помощью Steamworks.NET. Я последовал официальному руководству Steam по Godot и адаптировал его к своей собственной многопользовательской настройке. Передача голоса работает, но во время воспроизведения слышится потрескивание. Я пробовал изменять различные случайные значения, чтобы увидеть, изменилось ли что-нибудь, и добавил множество средств обработки ошибок и проверок, но мне не удалось найти источник потрескивающих звуков.
Чтобы записать голос игрока, я использую голосовую систему Steamworks. А затем, чтобы воспроизвести его, я использую Godot AudioStreamGeneratorPlayback, связанный с AudioStreamPlayer3D. Длина буфера генератора аудиопотока установлена равной 0,5f, а скорость смешивания установлена на возврат SteamUser.GetVoiceOptimalSampleRate().
Вот мой код, который я максимально упростил, но при этом сохранил управление ошибками для устранения возможных ошибок:
public partial class ProximityChat : Node3D
{
uint voiceSampleRate = 0;
private const int MaxCompressedBufferSize = 1024;
private const int MaxUncompressedBufferSize = 88200;
private byte[] reusableCompressedBuffer = new byte[MaxCompressedBufferSize];
private byte[] reusableUncompressedBuffer = new byte[MaxUncompressedBufferSize];
private double voiceProcessTimer = 0.0;
private const double VoiceProcessInterval = 1.0 / 20.0; // 20fps
public override void _Ready()
{
SteamUser.StartVoiceRecording();
ServerAbstract.Instance.OnVoiceDataReceived += HandleVoiceDataReceived;
voiceSampleRate = SteamUser.GetVoiceOptimalSampleRate();
}
public override void _Process(double delta)
{
voiceProcessTimer += delta;
if (voiceProcessTimer >= VoiceProcessInterval)
{
voiceProcessTimer = 0.0;
ProcessVoiceData();
}
}
private void ProcessVoiceData()
{
uint countCompressedData = 0;
EVoiceResult result = SteamUser.GetAvailableVoice(out countCompressedData);
if (countCompressedData >= MaxCompressedBufferSize)
{
GD.PrintErr("Voice data size exceeds buffer size.");
return;
}
if (result == EVoiceResult.k_EVoiceResultOK && countCompressedData > 0)
{
uint nBytesWritten = 0;
SteamUser.GetVoice(true, reusableCompressedBuffer, (uint)MaxCompressedBufferSize, out nBytesWritten);
MemoryStream memoryStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memoryStream);
writer.Write((byte)ServerAbstract.Instance.UserIndex);
writer.Write(nBytesWritten);
writer.Write(reusableCompressedBuffer, 0, (int)nBytesWritten);
ServerAbstract.Instance.SendToAll(memoryStream.ToArray(), (int)memoryStream.Length, ServerAbstractClass.DeliveryMethod.Unreliable);
}
}
private void HandleVoiceDataReceived(BinaryReader reader, byte[] dataBuffer)
{
byte userIndex = reader.ReadByte();
uint dataSize = reader.ReadUInt32();
byte[] compressedData = reader.ReadBytes((int)dataSize);
uint nBytesWritten = 0;
EVoiceResult result = SteamUser.DecompressVoice(compressedData, dataSize, reusableUncompressedBuffer, (uint)MaxUncompressedBufferSize, out nBytesWritten, voiceSampleRate);
if (result == EVoiceResult.k_EVoiceResultOK)
{
Character character = (Character)ReplicationServer.Instance.GetReplicatedObject(userIndex);
if (character?.voiceStreamPlayback == null)
{
GD.PrintErr("Character or voice stream playback is null.");
return;
}
int requiredFrames = (int)(nBytesWritten / 2);
if (!character.voiceStreamPlayback.CanPushBuffer(requiredFrames))
{
GD.PrintErr("Voice stream buffer full, dropping old data.");
return;
}
Vector2[] stereoData = new Vector2[requiredFrames];
for (int i = 0; i < requiredFrames; i++)
{
short sample = (short)(
reusableUncompressedBuffer[i * 2] |
(reusableUncompressedBuffer[i * 2 + 1]
Подробнее здесь: https://stackoverflow.com/questions/798 ... imity-chat