created centralized UDP Listener

This commit is contained in:
tom.hempel
2025-10-25 14:58:22 +02:00
parent 4ddb89d011
commit 3f72973ff5
10 changed files with 585 additions and 534 deletions

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Convai.Scripts.Runtime.Core;
@ -37,7 +36,6 @@ namespace Convai.Scripts.Runtime.Multiplayer
public int ListenPort => listenPort;
// Network components
private UdpClient _udpListener;
private IPEndPoint _remoteEndPoint;
private bool _isListening = false;
private int listenPort;
@ -47,24 +45,19 @@ namespace Convai.Scripts.Runtime.Multiplayer
private bool _isReceivingAudio = false;
private int _expectedSequence = 0;
private const uint MAGIC_NUMBER = 0xC0A1; // Simple magic number for packet validation
private const uint ACK_MAGIC = 0xC0A2; // ACK magic to confirm START control
// Timing for auto-stop
private float _lastPacketTime;
private const float AUTO_STOP_DELAY = 1.0f; // Stop listening after 1 second of no packets
// Packet structure (matching ConvaiSimpleUDPAudioSender)
// SIMPLIFIED Packet structure (matching ConvaiSimpleUDPAudioSender)
private struct AudioPacketData
{
public uint magicNumber;
public int sequence;
public int sampleCount;
public int microphonePosition;
public bool isEndSignal;
public bool isStartSignal;
public short[] audioSamples;
public long timestamp;
}
[Header("Recording Storage")]
@ -234,20 +227,22 @@ namespace Convai.Scripts.Runtime.Multiplayer
try
{
// Create UDP client with port reuse to allow sharing with UDPPeerDiscovery
_udpListener = new UdpClient();
_udpListener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_udpListener.Client.Bind(new IPEndPoint(IPAddress.Any, listenPort));
// Wait for shared listener to be ready
if (SharedUDPListener.Instance == null)
{
ConvaiLogger.Error("SharedUDPListener not found! Make sure it's in the scene.", ConvaiLogger.LogCategory.Character);
return;
}
// Subscribe to shared listener
SharedUDPListener.Instance.OnPacketReceived += HandlePacketReceived;
_isListening = true;
ConvaiLogger.Info($"Simple UDP Audio Receiver V2 listening on port {listenPort} (shared)", ConvaiLogger.LogCategory.Character);
// Start listening for incoming packets
_ = ListenForAudioPackets(_cancellationTokenSource.Token);
ConvaiLogger.Info($" Audio Receiver subscribed to shared listener, listening for magic 0x{MAGIC_NUMBER:X}", ConvaiLogger.LogCategory.Character);
}
catch (Exception ex)
{
ConvaiLogger.Error($"Failed to start UDP listener: {ex.Message}", ConvaiLogger.LogCategory.Character);
ConvaiLogger.Error($"❌ FAILED to subscribe Audio Receiver: {ex.Message}", ConvaiLogger.LogCategory.Character);
ConvaiLogger.Error($"Stack trace: {ex.StackTrace}", ConvaiLogger.LogCategory.Character);
}
}
@ -258,9 +253,12 @@ namespace Convai.Scripts.Runtime.Multiplayer
return;
_isListening = false;
_udpListener?.Close();
_udpListener?.Dispose();
_udpListener = null;
// Unsubscribe from shared listener
if (SharedUDPListener.Instance != null)
{
SharedUDPListener.Instance.OnPacketReceived -= HandlePacketReceived;
}
// Stop any ongoing simulation
StopTalkingSimulation();
@ -268,26 +266,19 @@ namespace Convai.Scripts.Runtime.Multiplayer
ConvaiLogger.Info("Stopped UDP Audio Receiver V2", ConvaiLogger.LogCategory.Character);
}
private async Task ListenForAudioPackets(CancellationToken cancellationToken)
private void HandlePacketReceived(byte[] data, IPEndPoint senderEndPoint)
{
try
{
while (_isListening && !cancellationToken.IsCancellationRequested)
{
var result = await _udpListener.ReceiveAsync();
_remoteEndPoint = result.RemoteEndPoint;
await ProcessReceivedPacket(result.Buffer, result.RemoteEndPoint);
}
}
catch (ObjectDisposedException)
{
// Normal when stopping
}
catch (Exception ex)
{
ConvaiLogger.Error($"Error in UDP listener: {ex.Message}", ConvaiLogger.LogCategory.Character);
}
// Check if this is an audio packet (by magic number)
if (data.Length < 12) return;
uint magic = BitConverter.ToUInt32(data, 0);
if (magic != MAGIC_NUMBER) return;
// Update remote endpoint
_remoteEndPoint = senderEndPoint;
// Process audio packet
_ = ProcessReceivedPacket(data, senderEndPoint);
}
private Task ProcessReceivedPacket(byte[] data, IPEndPoint sender)
@ -305,28 +296,17 @@ namespace Convai.Scripts.Runtime.Multiplayer
_totalPacketsReceived++;
_lastPacketReceivedTime = DateTime.UtcNow;
if (enableDebugLogging)
{
if (packet.isEndSignal)
ConvaiLogger.DebugLog($"Received end signal from {sender}", ConvaiLogger.LogCategory.Character);
else
ConvaiLogger.DebugLog($"Received audio packet {packet.sequence} with {packet.sampleCount} samples", ConvaiLogger.LogCategory.Character);
}
// SIMPLIFIED: Check for end signal (sampleCount == -1)
bool isEndSignal = (packet.sampleCount == -1);
// Handle START control: acknowledge and begin simulation
if (packet.isStartSignal)
if (enableDebugLogging && packet.sequence % 50 == 0)
{
SendStartAck(sender);
if (!_isReceivingAudio)
{
StartTalkingSimulation();
}
OnAudioReceiving?.Invoke(true);
return Task.CompletedTask;
ConvaiLogger.DebugLog($"📥 Received audio packet #{packet.sequence} (magic: 0x{packet.magicNumber:X}) from {sender}, {packet.sampleCount} samples", ConvaiLogger.LogCategory.Character);
}
if (packet.isEndSignal)
if (isEndSignal)
{
ConvaiLogger.Info($"📥 Received END signal from {sender}", ConvaiLogger.LogCategory.Character);
StopTalkingSimulation();
OnAudioReceiving?.Invoke(false);
}
@ -335,6 +315,7 @@ namespace Convai.Scripts.Runtime.Multiplayer
// If this is the first packet, start the talking simulation
if (packet.sequence == 0 && !_isReceivingAudio)
{
ConvaiLogger.Info($"📥 Received FIRST audio packet from {sender}, starting simulation", ConvaiLogger.LogCategory.Character);
StartTalkingSimulation();
}
@ -418,7 +399,7 @@ namespace Convai.Scripts.Runtime.Multiplayer
private AudioPacketData? ParseSimpleAudioPacket(byte[] data)
{
if (data.Length < 17) // Minimum header size to match sender
if (data.Length < 12) // SIMPLIFIED: Minimum header size (4+4+4)
return null;
try
@ -430,27 +411,22 @@ namespace Convai.Scripts.Runtime.Multiplayer
offset += 4;
if (magic != MAGIC_NUMBER)
{
if (enableDebugLogging)
ConvaiLogger.Warn($"❌ Invalid magic number: expected 0x{MAGIC_NUMBER:X}, got 0x{magic:X}", ConvaiLogger.LogCategory.Character);
return null;
}
// Read header (matching sender's 17-byte format)
// SIMPLIFIED header (12 bytes total)
int sequence = BitConverter.ToInt32(data, offset);
offset += 4;
int sampleCount = BitConverter.ToInt32(data, offset);
offset += 4;
int microphonePosition = BitConverter.ToInt32(data, offset);
offset += 4;
byte flags = data[offset];
offset += 1;
bool isEndSignal = (flags == 1);
bool isStartSignal = (flags == 2);
// Read audio data
// Read audio data (if not end signal)
short[] audioSamples = null;
if (!isEndSignal && !isStartSignal && sampleCount > 0)
if (sampleCount > 0)
{
int audioDataSize = sampleCount * sizeof(short);
if (data.Length >= offset + audioDataSize)
@ -458,6 +434,10 @@ namespace Convai.Scripts.Runtime.Multiplayer
audioSamples = new short[sampleCount];
Buffer.BlockCopy(data, offset, audioSamples, 0, audioDataSize);
}
else
{
ConvaiLogger.Warn($"⚠️ Packet too small: expected {offset + audioDataSize} bytes, got {data.Length}", ConvaiLogger.LogCategory.Character);
}
}
return new AudioPacketData
@ -465,11 +445,7 @@ namespace Convai.Scripts.Runtime.Multiplayer
magicNumber = magic,
sequence = sequence,
sampleCount = sampleCount,
microphonePosition = microphonePosition,
isEndSignal = isEndSignal,
isStartSignal = isStartSignal,
audioSamples = audioSamples,
timestamp = 0 // Not provided in sender format
audioSamples = audioSamples
};
}
catch (Exception ex)
@ -479,26 +455,6 @@ namespace Convai.Scripts.Runtime.Multiplayer
}
}
private void SendStartAck(IPEndPoint sender)
{
try
{
if (_udpListener == null || sender == null)
return;
byte[] ack = new byte[8];
BitConverter.GetBytes(ACK_MAGIC).CopyTo(ack, 0);
BitConverter.GetBytes(-1).CopyTo(ack, 4);
_udpListener.SendAsync(ack, ack.Length, sender);
if (enableDebugLogging)
ConvaiLogger.DebugLog($"Sent START ACK to {sender}", ConvaiLogger.LogCategory.Character);
}
catch (Exception ex)
{
ConvaiLogger.Warn($"Failed to send START ACK: {ex.Message}", ConvaiLogger.LogCategory.Character);
}
}
private void BufferAudioPacket(int sequence, short[] samples)
{