Нужна помощь в создании Unity + Dissonance Voice Relay гладко и синхронизирован (настройка тока имеет задержку и потрескC#

Место общения программистов C#
Ответить Пред. темаСлед. тема
Anonymous
 Нужна помощь в создании Unity + Dissonance Voice Relay гладко и синхронизирован (настройка тока имеет задержку и потреск

Сообщение Anonymous »

Я работаю над пользовательской системой голосового реле в Unity, используя NetCode для GameObjects и голосовой чат диссонанса. Система предназначена для трансляции голоса игрока А через другой аудиозаучитель независимо от расстояния, так что звук на основе близости работает правильно через 3D-аудио, в то же время позволяя слушать в рамках доступа к получению голоса.

Код: Выделить всё

The audio has a 1–2 second delay

The voice sounds choppy and robotic

There’s frequent crackling or stuttering

Increasing the frame size above 300+ breaks everything (no sound gets through)
Все, что я хочу, это то, чтобы голос был передан гладко, с минимальной задержкой, и в синхронизации с другими игроками, слышащими его с правильного аудиора.

Код: Выделить всё

using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;

[RequireComponent(typeof(AudioSource))]
public class AudioRelayTarget : NetworkBehaviour
{
private Queue buffer = new();
private const int MaxBuffer = 48000;
private AudioSource source;

public ulong OwnerClientId => GetComponent().OwnerClientId;

private void Awake()
{
source = GetComponent();
source.spatialBlend = 1;
source.loop = true;
source.clip = AudioClip.Create("RelayStream", MaxBuffer, 1, 48000, true, OnAudioRead);
source.Play();
}

public override void OnNetworkSpawn()
{
AudioRelayTargetRegistry.Register(OwnerClientId, this);
}

public void ReceiveAudio(float[] data)
{
lock (buffer)
{
foreach (var f in data)
{
buffer.Enqueue(f);
if (buffer.Count > MaxBuffer)
buffer.Dequeue();
}
}
}

private void OnAudioRead(float[] data)
{
lock (buffer)
{
for (int i = 0; i < data.Length; i++)
data[i] = buffer.Count > 0 ? buffer.Dequeue() : 0f;
}
}
}

[b] audioreLayTargetRegistry:  
using System.Collections.Generic;

public static class AudioRelayTargetRegistry
{
private static readonly Dictionary _registry = new();

public static void Register(ulong ownerId, AudioRelayTarget target)
{
if (!_registry.ContainsKey(ownerId))
_registry.Add(ownerId, target);
}

public static AudioRelayTarget Get(ulong ownerId)
{
_registry.TryGetValue(ownerId, out var t);
return t;
}
}
miclaysender: [/b]

Код: Выделить всё

using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using Dissonance;
using Dissonance.Audio.Capture;
using NAudio.Wave;

public class MicRelaySender : NetworkBehaviour, IMicrophoneSubscriber
{
private WaveFormat _format;
private readonly Queue micQueue = new();
private VoiceRelayManager _relayManager;
[SerializeField] int FrameSize = 240;

public override void OnNetworkSpawn()
{
if (IsOwner || IsServer)
{
var comms = FindObjectOfType();
comms.SubscribeToRecordedAudio(this);

_relayManager = FindObjectOfType();
if (IsOwner)
InvokeRepeating(nameof(SendMicFrame), 0f, 0.02f);
}
}
private void SendMicFrame()
{
if (!IsOwner) return;

float[] audio = null;

lock (micQueue)
{
if (micQueue.Count > 0)
audio = micQueue.Dequeue();
}

if (audio != null)
SendMicDataServerRpc(audio);
}
public void ReceiveMicrophoneData(ArraySegment buffer, WaveFormat format)
{
_format = format;

if (IsOwner &&  buffer.Count >= FrameSize)
{
float[] shortBuffer = new float[FrameSize];
Array.Copy(buffer.Array, buffer.Offset, shortBuffer, 0, FrameSize);
lock (micQueue)
{
micQueue.Enqueue(shortBuffer);
}
}
}

public void Reset()
{
lock (micQueue)
{
micQueue.Clear();
}
}

private void Update()
{
if (!IsOwner) return;

lock (micQueue)
{
while (micQueue.Count > 0)
{
var audio = micQueue.Dequeue();
SendMicDataServerRpc(audio);
}
}
}

[ServerRpc(Delivery = RpcDelivery.Unreliable)]
private void SendMicDataServerRpc(float[] audioSamples)
{
if (_relayManager == null) return;

ulong relayClientId = _relayManager.GetRelayTargetFor(OwnerClientId);

RelayAudioPacket packet = new RelayAudioPacket
{
relayOwnerClientId = relayClientId,
samples = audioSamples
};

SendToRelayClientRpc(packet);
}

[ClientRpc]
private void SendToRelayClientRpc(RelayAudioPacket packet)
{
var target = AudioRelayTargetRegistry.Get(packet.relayOwnerClientId);
target?.ReceiveAudio(packet.samples);
}
}
​​ retaudiopacket:

Код: Выделить всё

using Unity.Netcode;

public struct RelayAudioPacket : INetworkSerializable
{
public ulong relayOwnerClientId;
public float[] samples;

public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref relayOwnerClientId);

int length = samples == null ? 0 : samples.Length;
serializer.SerializeValue(ref length);

if (serializer.IsReader)
samples = new float[length];

for (int i = 0; i < length; i++)
serializer.SerializeValue(ref samples[i]);
}
}
unitymicrophonecapturewithptt:

Код: Выделить всё

using System;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Dissonance.Audio.Capture;
using NAudio.Wave;

namespace Dissonance.Audio.Capture
{
public class UnityMicrophoneCaptureWithPTT : MonoBehaviour, IMicrophoneCapture
{
public KeyCode pushToTalkKey = KeyCode.V;

public bool IsRecording { get; private set; }
public string Device { get; private set; }
public TimeSpan Latency { get; private set; }

private readonly List _subscribers = new();
private AudioClip _micClip;
private const int SampleRate = 48000;
private const int FrameSize = 9600;
private float[] _frameBuffer = new float[FrameSize];
private int _micPosition = 0;
private float _updateTimer;

public WaveFormat StartCapture(string name)
{
if (Microphone.devices.Length == 0)
{
Debug.LogError("[MicCapture] No microphone devices found!");
return null;
}

if (!string.IsNullOrEmpty(name) && !Microphone.devices.Contains(name))
{
Debug.LogWarning($"[MicCapture] Microphone '{name}' not found.  Using default.");
name = null;
}

Device = name ?? Microphone.devices[0];
_micClip = Microphone.Start(Device, true, 1, SampleRate);

if (_micClip == null)
{
Debug.LogError("[MicCapture] Failed to start microphone.");
return null;
}

IsRecording = true;
Latency = TimeSpan.Zero;
_micPosition = 0;
return new WaveFormat(SampleRate, 1);
}
private void Update()
{
if (IsRecording && Input.GetKey(pushToTalkKey))
{
UpdateSubscribers();
}
}
public void StopCapture()
{
if (_micClip != null)
{
Microphone.End(Device);
_micClip = null;
}

IsRecording = false;
}

public void Subscribe(IMicrophoneSubscriber listener)
{
if (!_subscribers.Contains(listener))
_subscribers.Add(listener);
}

public bool Unsubscribe(IMicrophoneSubscriber listener)
{
return _subscribers.Remove(listener);
}

public bool UpdateSubscribers()
{
if (!IsRecording || _micClip == null || !Input.GetKey(pushToTalkKey))
return false;

_updateTimer += Time.unscaledDeltaTime;

while (_updateTimer >= 0.02f)
{
_updateTimer -= 0.02f;

int micPos = Microphone.GetPosition(Device);
int totalSamples = _micClip.samples;

if (micPos < _micPosition)
_micPosition = 0;

if (micPos - _micPosition < FrameSize)
return false;

_micClip.GetData(_frameBuffer, _micPosition);
_micPosition = (_micPosition + FrameSize) % totalSamples;

foreach (var subscriber in _subscribers)
{
subscriber.ReceiveMicrophoneData(new ArraySegment(_frameBuffer), new WaveFormat(SampleRate, 1));
}
}

return false;
}
}
}
< /code>
voicerelaymanager: < /p>
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;

public class VoiceRelayManager : NetworkBehaviour
{

[Header("Prefab to spawn for each player")]
public GameObject relaySpeakerPrefab;
private Dictionary _relaySpeakers = new();
private Dictionary _speakerToRelay = new();

public override void OnNetworkSpawn()
{
if (IsServer)
NetworkManager.OnClientConnectedCallback += OnClientConnected;
}

private void OnClientConnected(ulong clientId)
{
SpawnRelaySpeaker(clientId);
}

private void SpawnRelaySpeaker(ulong clientId)
{
GameObject relayObj = Instantiate(relaySpeakerPrefab);
var netObj = relayObj.GetComponent();
netObj.SpawnWithOwnership(clientId);

_relaySpeakers[clientId] = relayObj;

var playerObj = NetworkManager.Singleton.ConnectedClients[clientId].PlayerObject;
var relayNetId = netObj.NetworkObjectId;
var playerNetId = playerObj.NetworkObjectId;

StartCoroutine(AssignFollowTargetWhenReady(relayNetId, playerNetId));
}

public ulong GetRelayTargetFor(ulong speakerClientId)
{
if (_speakerToRelay.TryGetValue(speakerClientId, out var relayClientId))
return relayClientId;

return speakerClientId;
}

public AudioRelayTarget FindRelayTargetInstance(ulong relayOwnerId)
{
if (_relaySpeakers.TryGetValue(relayOwnerId, out var go))
return go.GetComponent();

return null;
}

private System.Collections.IEnumerator AssignFollowTargetWhenReady(ulong relayId, ulong playerId)
{
yield return new WaitForSeconds(0.1f);

if (NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(relayId, out var relay) &&
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(playerId, out var player))
{
var follow = relay.GetComponent();
if (follow != null)
follow.SetTarget(player.transform);
}
else
{
Debug.LogWarning($"[VoiceRelay] Relay or Player not found: {relayId}, {playerId}");
}
}
}
< /code>
Я не использую VoiceProximityBroadcastTrigger только VoiceProximityReceiptTrigger. Пользовательский RelaySpeaker, который я создал вместо VoiceProximityBroadcastTrigger, использует настройки AudioSource по умолчанию, с его диапазоном, установленным 500. VoiceProximityReceipttrigger установлен на 3. Таким образом, голос может быть передан из любого места, но только те, кто находится в диапазоне AudioSource, могут услышать его. Единственная проблема заключается в том, что переданный голос искажен.How can I eliminate the delay and crackling?

Is there a better way to synchronize voice data for near real-time relay via AudioSource?

How do I match Dissonance’s own processing quality (VAD, buffering, etc.)?

p> https://i.sstatic.net/4awm39oL. />

Подробнее здесь: https://stackoverflow.com/questions/796 ... rent-setup
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «C#»