Placeholder-Software / Dissonance

Unity Voice Chat Asset
71 stars 5 forks source link

[help] Dissonance setup on a dedicated server #217

Closed fender closed 3 years ago

fender commented 3 years ago

I'm running Dissonance on my game of which we're hosting dedicated servers. Our servers are basically like any other game client, except they do not spawn a player into the game world and just manage the game state.

From what I can tell, the server client is trying to actually capture/playback audio. How can I turn that off but still ensure that audio data is relayed between the clients?

These are the logs when my game server starts the scene:

[Dissonance:Core] (15:17:32.635) DissonanceComms: Starting Dissonance Voice Comms (6.4.6)
- Network: [DissonanceSetup(Clone) (Dissonance.Integrations.PhotonBolt.BoltCommsNetwork)]
- Quality Settings: [Quality: Medium, FrameSize: Medium, FEC: True, DenoiseAmount: High, VoiceDuckLevel: 0.75 VAD: MediumSensitivity]
- Codec: [Codec: Opus, FrameSize: 1920, SampleRate: 48kHz] 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

[Dissonance:Network] (15:17:32.660) BoltServer: Created server with SessionId:1322673248 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

[Dissonance:Network] (15:17:32.661) BoltServer: Connected 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

[Dissonance:Network] (15:17:32.681) ConnectionNegotiator`1: Received handshake response from server, joined session '1322673248' 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

[Dissonance:Recording] (15:17:32.717) BasicMicrophoneCapture: No microphone detected; disabling voice capture 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

[Dissonance:Recording] (15:17:32.717) CapturePipelineManager: Failed to start microphone capture; local voice transmission will be disabled. 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

Audio system is disabled, so AudioSettings.outputSampleRate cannot be queried. Please check the audio project settings. 
(Filename:  Line: 26)

ArgumentException: Frequency in created clip must be greater than 0
  at UnityEngine.AudioClip.Create (System.String name, System.Int32 lengthSamples, System.Int32 channels, System.Int32 frequency, System.Boolean stream, UnityEngine.AudioClip+PCMReaderCallback pcmreadercallback, UnityEngine.AudioClip+PCMSetPositionCallback pcmsetpositioncallback) [0x00048] in <23d776825a8f46dcb0d8cf9e486df0e6>:0 
  at UnityEngine.AudioClip.Create (System.String name, System.Int32 lengthSamples, System.Int32 channels, System.Int32 frequency, System.Boolean stream, UnityEngine.AudioClip+PCMReaderCallback pcmreadercallback) [0x00001] in <23d776825a8f46dcb0d8cf9e486df0e6>:0 
  at Dissonance.Audio.Playback.VoicePlayback.OnEnable () [0x00056] in <4047c9e392a84f0d924ae6a06d50f7c7>:0 
UnityEngine.GameObject:SetActive(Boolean)
Dissonance.DissonanceComms:Net_PlayerJoined(String, CodecSettings)
Dissonance.Networking.BaseCommsNetwork`5:OnPlayerJoined(String, CodecSettings)
Dissonance.Networking.Client.EventQueue:InvokeEvent(String, CodecSettings, Action`2)
Dissonance.Networking.Client.EventQueue:DispatchEvents(Nullable`1)
Dissonance.Networking.BaseClient`3:RunUpdate(DateTime)
Dissonance.Networking.BaseClient`3:Update()
Dissonance.Networking.Session:Update()
Dissonance.Networking.BaseCommsNetwork`5:Update()
Dissonance.Integrations.PhotonBolt.BoltCommsNetwork:Update()

(Filename: <23d776825a8f46dcb0d8cf9e486df0e6> Line: 0)

For Dissonance to work for all clients connected, do I need to leave DissonanceComms enabled so that it relays the data?

martindevans commented 3 years ago

Dissonance starts itself in the same mode as the underlying network system. In your case it looks like that's Photon Bolt, so you can find the relevant code in Assets/Dissonance/Integrations/PhotonBolt/BoltCommsNetwork.cs on line 226:

if (BoltNetwork.IsConnected)
{
    if (BoltNetwork.IsServer)
    {
        if (Mode != NetworkMode.Host)
            RunAsHost(Unit.None, Unit.None); // <-----
    }
    else
    {
        if (Mode != NetworkMode.Client)
            RunAsClient(Unit.None);          // <-----
    }
}
else
{
    if (Mode != NetworkMode.None)
        Stop();                              // <-----
}

The lines that I have marked change the Dissonance network mode. As you can see there's not one which starts Dissonance as a dedicated server, only as a Host (mixed client/server). I'm not sure why this is and I'll definitely look into it in more detail once I'm back full time in January.

You can probably just add another case like this (assuming that Bolt sets both the IsClient and IsServer flags when it's a host):

    if (BoltNetwork.IsServer)
    {
        if (BoltNetwork.IsClient)
        {
            if (Mode != NetworkMode.Host)
                RunAsHost(Unit.None, Unit.None); // <----- Start as host (client & server)
        }
        else
        {
            if (Mode != NetworkMode.DedicatedServer)
                RunAsDedicatedServer(Unit.None); // <----- Start as just a server
        }
    }
fender commented 3 years ago

We can assume that when BoltNetwork.IsServer is true then we're running a dedicated server build. However, the edge case in Bolt is that you can run a single player game offline where both BoltNetwork.IsServer and BoltNetwork.IsSinglePlayer become true. Would something like this suffice?

if (BoltNetwork.IsServer)
    {
        if (BoltNetwork.IsSinglePlayer)
        {
            if (Mode != NetworkMode.Host)
                RunAsHost(Unit.None, Unit.None); // <----- Start as host (client & server)
        }
        else
        {
            if (Mode != NetworkMode.DedicatedServer)
                RunAsDedicatedServer(Unit.None); // <----- Start as just a server
        }
    }
martindevans commented 3 years ago

If it's single player you might just want to do this instead of running as host:

if (Mode != NetworkMode.None)
    Stop();

This way Dissonance is totally deactivated in single player mode.

fender commented 3 years ago

Good shout. My dedicated server seems to have created the server correctly:

[Dissonance:Core] (16:35:14.632) DissonanceComms: Loading default playback prefab 
[Dissonance:Core] (16:35:14.641) DissonanceComms: Starting Dissonance Voice Comms (6.4.6)
- Network: [DissonanceSetup(Clone) (Dissonance.Integrations.PhotonBolt.BoltCommsNetwork)]
- Quality Settings: [Quality: Medium, FrameSize: Medium, FEC: True, DenoiseAmount: High, VoiceDuckLevel: 0.75 VAD: MediumSensitivity]
- Codec: [Codec: Opus, FrameSize: 1920, SampleRate: 48kHz] 
[Dissonance:Network] (16:35:14.667) BoltServer: Created server with SessionId:1015807102 
[Dissonance:Network] (16:35:14.667) BoltServer: Connected 

However, the first client that joined got a new error. This caused them to become self-muted (If I try to uncheck Mute in Dissonance Comms once playing as the client via the editor, it doesn't uncheck and gives me a different error).

[Dissonance:Recording] (16:35:35.913) CapturePipelineManager: Unexpected exception encountered starting microphone capture; local voice transmission will be disabled: System.ArgumentException: TimeSpan does not accept floating point Not-a-Number values.
  at System.TimeSpan.Interval (System.Double value, System.Int32 scale) [0x00012] in <9577ac7a62ef43179789031239ba8798>:0 
  at System.TimeSpan.FromSeconds (System.Double value) [0x00000] in <9577ac7a62ef43179789031239ba8798>:0 
  at Dissonance.Audio.Capture.BasicMicrophoneCapture.StartCapture (System.String inputMicName) [0x00218] in /Users/joefender/Workspace/Seance/Assets/Plugins/Dissonance/Core/Audio/Capture/BasicMicrophoneCapture.cs:132 
  at Dissonance.Audio.Capture.CapturePipelineManager.RestartTransmissionPipeline (System.String reason) [0x0004f] in /Users/joefender/Workspace/Seance/Assets/Plugins/Dissonance/Core/Audio/Capture/CapturePipelineManager.cs:339 
UnityEngine.Debug:LogError(Object)
Dissonance.LogMessage:Log() (at Assets/Plugins/Dissonance/Core/Log.cs:66)
Dissonance.Logs:SendLogMessage(String, LogLevel) (at Assets/Plugins/Dissonance/Core/Log.cs:95)
Dissonance.Log:WriteLog(LogLevel, String) (at Assets/Plugins/Dissonance/Core/Log.cs:178)
Dissonance.Log:WriteLogFormat(LogLevel, String, Exception) (at Assets/Plugins/Dissonance/Core/Log.cs:187)
Dissonance.Log:Error(String, Exception) (at Assets/Plugins/Dissonance/Core/Log.cs:428)
Dissonance.Audio.Capture.CapturePipelineManager:RestartTransmissionPipeline(String) (at Assets/Plugins/Dissonance/Core/Audio/Capture/CapturePipelineManager.cs:381)
Dissonance.Audio.Capture.CapturePipelineManager:Update(Boolean, Single) (at Assets/Plugins/Dissonance/Core/Audio/Capture/CapturePipelineManager.cs:204)
Dissonance.DissonanceComms:Update() (at Assets/Plugins/Dissonance/DissonanceComms.cs:612)
martindevans commented 3 years ago

The only use of FromSeconds in BasicMicrophoneCapture is this line:

Latency = TimeSpan.FromSeconds(frameSize / (float)_format.SampleRate);

Could you check what values are being passed into that? For it to be creating a NaN value it looks like _clip.frequency must be zero, which isn't a valid thing for the Microphone API to return.

fender commented 3 years ago

@martindevans Thanks, that helped pinpoint the issue. Turns out I had an audio issue with my Unity build so please disregard my previous comment.

So I have 2 clients connected to a dedicated server. But for some reason data isn't being sent over the network to the Server after pushing to talk. I can confirm that "(speaking)" is shown next to my local player in the DissonanceComms inspector.

image
martindevans commented 3 years ago

Do you have matching VoiceBroadcastTrigger (on the speaking side) and VoiceReceiptTrigger (on the receiving side) components? Dissonance only sends voice across the network when it needs to.

Sorry for the slow reply. I'm only just back off my Christmas/New Year break :)

fender commented 3 years ago

@martindevans Ah, I see. So I've just added the receipt trigger so now my single DissonanceSetup prefab looks like this:

image

This is being instantiated both on clients and the dedicated server, is there any issues with that? Also, note that we have a custom CFVoiceBroadcastTrigger component that inherits from VoiceBroadcastTrigger only to override the IsUserActivated function.

I'm seeing an issue, possible race condition as it doesn't happen every time, where as soon as a client joins a dedicated server it disconnects and then goes into an infinite loop of trying to start the server itself.

Dedicated server logs:

[Dissonance:Core] (15:15:03.934) DissonanceComms: Starting Dissonance Voice Comms (6.4.6)
- Network: [DissonanceSetup(Clone) (Dissonance.Integrations.PhotonBolt.BoltCommsNetwork)]
- Quality Settings: [Quality: Medium, FrameSize: Medium, FEC: True, DenoiseAmount: High, VoiceDuckLevel: 0.75 VAD: MediumSensitivity]
- Codec: [Codec: Opus, FrameSize: 1920, SampleRate: 48kHz] 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

[Dissonance:Network] (15:15:04.039) BoltServer: Created server with SessionId:655596750 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

[Dissonance:Network] (15:15:04.044) BoltServer: Connected 
(Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)

Then the client connects and gets these logs:

image

Apologies for the 101 questions here. We've had success (twice) with Dissonance on games before but this is our first time doing dedicated servers. Appreciate the help!

fender commented 3 years ago

@martindevans Forgot to mention probably the most important part. This is our current BoltCommsNetwork.cs Update loop for the PhotonBolt integration:

        protected override void Update()
        {
            if (IsInitialized)
            {
                if (BoltNetwork.IsConnected)
                {
                    if (BoltNetwork.IsServer)
                    {
                        if (BoltNetwork.IsSinglePlayer)
                        {
                            if (Mode != NetworkMode.Host)
                                RunAsHost(Unit.None, Unit.None);
                        }
                        else
                        {
                            if (Mode != NetworkMode.DedicatedServer)
                                RunAsDedicatedServer(Unit.None);
                        }
                    }
                    else
                    {
                        if (Mode != NetworkMode.Client)
                            RunAsClient(Unit.None);
                    }
                }
                else
                {
                    if (Mode != NetworkMode.None)
                        RunAsHost(Unit.None, Unit.None);
                }
            }

            base.Update();
        }
martindevans commented 3 years ago

The two items in the log that catch my eye are:

The second one is shutting down the client (because the first error happened). This will then trigger the RunAsThing code to restart the client next frame - if you're always getting that fatal error that explains the disconnection loop. Can you have a closer look at the stacktrace for the first error (reference not set to instance of object) and see exactly what's null? I think that's the root problem.

Just a small note: the two RunAsHost items there don't look like what you want. You're running Dissonance as a host (i.e. a local server and a local client) in two cases; when it's in single player mode or when it's not in a session at all. I think you probably want to run nothing in these cases (i.e. call Stop(); here instead).

fender commented 3 years ago

@martindevans Yeah, I took a look at the error and it seems to be from within an update loop of one of your scripts but I couldn't make out the exact cause here.

[Dissonance:Network] (15:18:30.144) BoltClient: Object reference not set to an instance of an object
UnityEngine.Debug:LogError(Object)
Dissonance.LogMessage:Log() (at Assets/Plugins/Dissonance/Core/Log.cs:66)
Dissonance.Logs:SendLogMessage(String, LogLevel) (at Assets/Plugins/Dissonance/Core/Log.cs:95)
Dissonance.Log:WriteLog(LogLevel, String) (at Assets/Plugins/Dissonance/Core/Log.cs:178)
Dissonance.Log:Error(String) (at Assets/Plugins/Dissonance/Core/Log.cs:422)
Dissonance.Networking.BaseClient`3:RunUpdate(DateTime) (at Assets/Plugins/Dissonance/Core/Networking/BaseClient.cs:207)
Dissonance.Networking.BaseClient`3:Update() (at Assets/Plugins/Dissonance/Core/Networking/BaseClient.cs:175)
Dissonance.Networking.Session:Update() (at Assets/Plugins/Dissonance/Core/Networking/BaseCommsNetwork.cs:159)
Dissonance.Networking.BaseCommsNetwork`5:Update() (at Assets/Plugins/Dissonance/Core/Networking/BaseCommsNetwork.cs:330)
Dissonance.Integrations.PhotonBolt.BoltCommsNetwork:Update() (at Assets/Dissonance/Integrations/PhotonBolt/BoltCommsNetwork.cs:261)

In regards to Stop() calls, we're using Dissonance still in single player to capture back the audio buffer for us when someone speaks so we can process our speech recognition tasks. It makes it easier if we don't have to change that code so hopefully its OK to just keep calling RunAsHost() in those scenarios?

martindevans commented 3 years ago

BaseClient (Assets/Plugins/Dissonance/Core/Networking/BaseClient.cs) has a try/catch block which contains the entire update process (Line 192). Could you temporarily comment out this block? That will hopefully give you a more useful stacktrace (at the moment it just points into the catch block).

we're using Dissonance still in single player

That's fine then, RunAsHost is the right approach for that :)

fender commented 3 years ago

@martindevans Sure, done:

image

It kinda seems like it disconnects before it even receives the first error, then tries to connect as the server which in turn causes the first error?

martindevans commented 3 years ago

It appears that it's failing in this method:

public void SendReliableToServer(ArraySegment<byte> data)
{
    if (BoltNetwork.IsServer)
        SendPacketReceived(BoltPeer.Local, data, _serverPacketListeners);
    else
    {
        var packet = DissonanceToServer.Create(GlobalTargets.OnlyServer); // <--------------- This is line 127 for me. Might not line up for you if you've added any lines while debugging.
        packet.BinaryData = ToDirectArray(data);
        packet.Send();
    }
}

Could you drop in a breakpoint there and check exactly what is null?

The two most likely possibilities are:

fender commented 3 years ago

@martindevans That was a huge help, thank you. It helped me track down an issue with the Bolt configuration as DissonanceToServer.Create was returning null. Turns out you must use Manual accept mode for dedicated servers with Bolt or things get all weird. I can confirm things are not working with Dissonance and multiple clients on the dedicated servers.

We've also got all the speech recognition stuff I mentioned elsewhere set up now for this game too. Dissonance has been such a valuable asset for us so far so thank you. (This game is an upcoming mobile one, not DEVOUR)