OnlineSubsystemNull: Listen Server w/out microphone will drop clients with microphone if voice is enabled

Note: I am using the UE4 source from the release branch of UE4.15.0

Using OnlineSubsystemNull, with voice chat properly enabled, and if a Listen Server has no microphone, and if a connecting Client does have a microphone, and the connecting Client’s voice is enabled and broadcasting, and if the Listen Server receives any voice data from the connecting Client, the Listen Server will kick the connecting Client immediately. Debugging the engine’s source code, the reason this happens is slightly convoluted, as follows…

The problem begins when FOnlineSubsystemNull::GetVoiceInterface() attempts to Init() its FOnlineVoiceImpl implementing IOnlineVoice on the Listen Server:

IOnlineVoicePtr FOnlineSubsystemNull::GetVoiceInterface() const
{
	if (VoiceInterface.IsValid() && !bVoiceInterfaceInitialized)
	{	
		if (!VoiceInterface->Init())
		{
			VoiceInterface = nullptr;
		}

		bVoiceInterfaceInitialized = true;
	}

	return VoiceInterface;
}

Looking at FOnlineVoiceImpl::Init(), we see:

bool FOnlineVoiceImpl::Init()
{
	bool bSuccess = false;

	// irrelevant code...

	bool bHasVoiceEnabled = false;
	if (GConfig->GetBool(TEXT("OnlineSubsystem"), TEXT("bHasVoiceEnabled"), bHasVoiceEnabled, GEngineIni) && bHasVoiceEnabled)
	{
		// irrelevant code...

		if (bSuccess)
		{
			const bool bVoiceEngineForceDisable = OnlineSubsystem->IsDedicated() || GIsBuildMachine;
			if (!bVoiceEngineForceDisable)
			{
				VoiceEngine = MakeShareable(new FVoiceEngineImpl(OnlineSubsystem));
				bSuccess = VoiceEngine->Init(MaxLocalTalkers, MaxRemoteTalkers);
			}

			// irrelevant code...
		}

		// irrelevant code...
	}

	// irrelevant code...

	return bSuccess;
}

Which calls bSuccess = VoiceEngine->Init(MaxLocalTalkers, MaxRemoteTalkers);, so looking at FVoiceEngineImpl::Init(), we see:

bool FVoiceEngineImpl::Init(int32 MaxLocalTalkers, int32 MaxRemoteTalkers)
{
	bool bSuccess = false;

	if (!OnlineSubsystem->IsDedicated())
	{
		FVoiceModule& VoiceModule = FVoiceModule::Get();
		if (VoiceModule.IsVoiceEnabled())
		{
			VoiceCapture = VoiceModule.CreateVoiceCapture();
			VoiceEncoder = VoiceModule.CreateVoiceEncoder();

			bSuccess = VoiceCapture.IsValid() && VoiceEncoder.IsValid();

			// irrelevant code...
		}

		// irrelevant code...
	}

	return bSuccess;
}

Which is where we start to see potential problems, namely with VoiceCapture = VoiceModule.CreateVoiceCapture();. I could continue to go deeper into the code, but suffice it to say that because a microphone doesn’t exist, bSuccess returns as false, and the FOnlineSubsystemNull::VoiceInterface->Init(); fails, which causes the entire FOnlineSubsystemNull::VoiceInterface to remain nullptr throughout the rest of the program’s execution. This looks like it should be fine, after all, code exists to account for a nullptr FOnlineSubsystemNull::VoiceInterface, but the real problem arises later on, when our first microphone-enabled Client joins our game: this Client’s FOnlineSubsystemNull::VoiceInterface is not nullptr because their microphone does exist, and therefore, they begin to send voice packets to the Listen Server, and their serialization is at some point delegated to UVoiceChannel::ReceivedBunch():

void UVoiceChannel::ReceivedBunch(FInBunch& Bunch)
{
	if (Connection->Driver && Connection->Driver->World)
	{
		while (!Bunch.AtEnd())
		{
			// Give the data to the local voice processing
			TSharedPtr<FVoicePacket> VoicePacket = UOnlineEngineInterface::Get()->SerializeRemotePacket(Connection->Driver->World, Bunch);
			if (VoicePacket.IsValid())
			{
				// irrelevant code...
			}
			else
			{
				// Unable to serialize the data because the serializer doesn't exist or there was a problem with this packet
				Bunch.SetError();
				break;
			}
		}
	}
}

And, again, looking at UOnlineEngineInterfaceImpl::SerializeRemotePacket():

TSharedPtr<FVoicePacket> UOnlineEngineInterfaceImpl::SerializeRemotePacket(UWorld* World, FArchive& Ar)
{
	IOnlineVoicePtr VoiceInt = Online::GetVoiceInterface(World);
	if (VoiceInt.IsValid())
	{
		return VoiceInt->SerializeRemotePacket(Ar);
	}
	return nullptr;
}

We see that it will return an invalid VoicePacket, because of the null FOnlineSubsystemNull::VoiceInterface, causing Bunch.SetError(); to be called. We now travel up the callstack to UChannel::ReceivedRawBunch() where we encounter…

UE_LOG( LogNetTraffic, Error, TEXT( "UChannel::ReceivedRawBunch: Bunch.IsError() after ReceivedNextBunch 1" ) );

…which finally causes UNetConnection::ReceivedPacket() to log the following error and disconnect the client for fear of a server crash attack.

UE_LOG( LogNetTraffic, Error, TEXT("Received corrupted packet data from client %s. Disconnecting."), *LowLevelGetRemoteAddress() );

Given all of this diagnostic info, it looks as though there is a structural weakness in the design of the voice system for the given case. A recommended fix that would not break the current design at a high level, or create messy code would be to support the FVoiceEngineImpl existing without a VoiceCapture, and just passing through silence should a steam of voice data be necessary.

Hey NMouzourakis,

If you have a recommended fix for the issue that you’re seeing, you can feel free to submit a pull request for our developers to consider. You can do so at the following link: https://github.com/EpicGames/UnrealEngine/compare?expand=1

I’ll be looking into the issue to see if I can reproduce it in the meantime to get a bug report in, but it’s worth looking into submitting a pull request if you’ve got a proposed solution as well.

Hi Sean,
No proposed fix as of yet; we’re using the OnlineSubsystemSteam for our VoIP builds for now since it doesn’t have this issue, but it was quite a pain to find the underlying cause, so I figured I would save one of your programmers the trouble. Also, I don’t feel quite comfortable enough with the Voice/Networking architecture to submit a pull request that would change it’s behavior in the way I mentioned, as there may be an underlying reason for the design. My recommendation was just a starting point based on programmer’s intuition. If I have some down time, I’ll look into it further, but this would be my first pull request, so I’ll have to learn the ropes with git a bit as well. (my team is on perforce)

I apologize for the delay. I’ve spent some time looking into this, but I’m not sure I was able to successfully mirrored your setup, as I wasn’t able to get the same results. I’m sure I’m overlooking something.

Would you be able to provide a simplified test project that showcases the issue so I can take a closer look and reproduce it on my end?

Hi Sean,

unfortunately I’m home sick today, and I only have a single Mac with me, but I can offer a couple “gotchas” while trying to reproduce the bug; sorry in advance if they seem obvious:

  • The voice has to be enabled in the standard way for the OnlineSubsystemNull. (How can I make in game voice communication? - Audio - Unreal Engine Forums)

  • The build has to be packaged for Win64 and running side-by-side on two different computers on the same network. (Client & Listen Server) The Client must have a default microphone and speaker set (right click on the sound icon in the windows tray to check) and the Listen server should have a default speaker set, but no default microphone set.

  • Make sure the client is actually trying to transmit voice. The code to do so seems to be marked “exec,” (class APlayerController::ToggleSpeaking(bool bInSpeaking)) so you should be able to enable that on the Client computer through the console without code, via something like, ToggleSpeaking 1. Doing this both before and after connecting to the Listen Server twice should ensure that at least a few packets of Voice are transmitted.

  • If the Client is connecting successfully, but you can’t hear the voice on the Listen Server, check that voice is enabled and broadcasting and the check that the mic is set on the Client on the OS level. (Sound menu in the system tray)

  • If you can hear the Client, then the Listen Server probably has a default microphone set on the OS level. Be careful, as some monitors/webcams/desktops might have microphones built-in, and the OS might pick up on those even if one isn’t plugged into the mic port directly. (Use the sound menu in the system tray to be sure.)

  • Finally, if it helps, my Listen Server was an HP Z440 Workstation with speaker-only headphones, mouse, keyboard, Ethernet, and three monitors plugged in, with an GeForce GTX 970, running Windows 7 Pro, and my Client was a 4-year-old Alienware Desktop, (model name eludes me, apologies) with Turtle Beach PLa headset (speakers & mic), mouse, keyboard, Ethernet, and one monitor plugged in to it, with its stock nVidia card (model eludes me again, apologies) and Windows 10 running. (OS may matter on the Listen Sever, since the Voice Implementation depends on the OS’ sound implementation, not quite sure without re-testing it, but Client OS shouldn’t matter)
  • If you could let me know exactly what results you are getting, I may be able to help further. Sorry if the bug is taking more time to detect/fix than it is worth.

I have this in my DefaultEngine.ini for an online Steam voice project. I’m not sure if it helps but I heard voice needs greater than the default 10k per client.

[/Script/OnlineSubsystemUtils.IpNetDriver]
MaxClientRate=30000
MaxInternetClientRate=60000
 
[/Script/Engine.Player]
ConfiguredInternetSpeed=30000
ConfiguredLanSpeed=60000

Maybe worth a try. Good luck.

Hi IslandPlaya,

I believe this is actually a separate bug. It has its own page at https://answers.unrealengine.com/questions/500778/voip-unstable-on-steam-network-saturated.html. This bug is a little different, because instead of the voice just cutting out occasionally, the Listen Server will actually kick the Client as a safety precaution. We use the fix you mentioned for the other bug as well, though. :slight_smile:

Thanks for the link. Sorry I can’t be more help at the mo. Good luck.

Hey NMouzourakis,

Sorry for the delay on this. I’ve spent some time getting a project set up and testing this, but I’m not able to see the same results. It’s possible that I’m overlooking something in my repro, so if you are able to provide me with a project that I can use to test the issue on my end, we can continue to investigate this issue. However, at this time, without a repro case, we are unable to take any further action.

If you’d like to provide additional information, feel free to leave a comment to reopen the thread and I’ll be glad to continue to look into the issue.

Have a great day

Hi Sean,

No problem man, I appreciate you looking into it. In the end, it might just be a hardware peculiarity on my side. But either way, it’s not a dealbreaker for us, so I’ll have to leave it on ice until I can get a project set up to help you test it.

I’m just curious on what happened on your side, though? Were you able to hear the Client on the Listen Server? Or not? Also, if you still have the project you were working on, you can attach it, and it might be easier for me to repro it, or modify and repro it on my side.

Either way, thanks for taking the time to look into it. :slight_smile:
NMouzourakis

I spoke too soon. I had some extra time so I took another look at it and it definitely does exactly as you stated. The client was kicked as soon as they began to transmit voice. Turns out I didn’t have the correct microphone disabled on the OS level. Sorry about that.

Thanks a lot for all of your information and for your patience. You can track the status of the ticket using the link below:

Have a great day

Ah, thank you, Sean! I thought I was going crazy for a sec lol. Yeah, as long as the devs can get to this page they shouldn’t have too much trouble fixing it. Thank you very much for all your patience and help. :slight_smile:

I just came across the symptoms of this issue - listen server without microphone configured will disconnect client with microphone as soon as voice starts transmitting - while using steam online subsystem in 4.22

Any news on this Just got this with 5.0.2