My team would like to add voice input to our UE4 mobile game using 4.18.3. I have followed the steps outlined in https://answers.unrealengine.com/questions/347976/basic-microphone-input-with-ue4.html. I have used much of his code, adding some logic to kill the session when the game is closed. On both PIE and Android, I consistently get voice capture state NoData with zero bytes available. I am at the limit of my understanding of the engine, and I was hoping someone might be able to spot a place to fix this. I will outline my solution and code below.
DefaultEngine.ini has these entries added:
[OnlineSubsystem]
DefaultPlatformService=Null
bHasVoiceEnabled=true
[Voice]
bEnabled=true
I have a custom gameinstance that initializes a session with OnlineSubsystemNull, using a public InitSession method that is called by the start-up level’s blueprint.
UFairiesGameInstance::UFairiesGameInstance(const FObjectInitializer & ObjectInitializer) : Super(ObjectInitializer)
{
OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &UFairiesGameInstance::OnCreateSessionComplete);
OnStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &UFairiesGameInstance::OnStartOnlineGameComplete);
OnDestroySessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &UFairiesGameInstance::OnDestroySessionComplete);
}
bool UFairiesGameInstance::HostSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers)
{
UE_LOG(LogTemp, Warning, TEXT("In HostSession"));
IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid() && UserId.IsValid())
{
SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->bIsLANMatch = bIsLAN;
SessionSettings->bUsesPresence = bIsPresence;
SessionSettings->NumPublicConnections = MaxNumPlayers;
SessionSettings->NumPrivateConnections = 0;
SessionSettings->bAllowInvites = true;
SessionSettings->bAllowJoinInProgress = true;
SessionSettings->bShouldAdvertise = true;
SessionSettings->bAllowJoinViaPresence = true;
SessionSettings->bAllowJoinViaPresenceFriendsOnly = false;
SessionSettings->Set(SETTING_MAPNAME, FString("MAP_Main_Menu"), EOnlineDataAdvertisementType::ViaOnlineService);
OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
return Sessions->CreateSession(*UserId, SessionName, *SessionSettings);
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("No OnlineSubsytem found!"));
}
return false;
}
void UFairiesGameInstance::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnStartSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
Sessions->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
}
}
if (bWasSuccessful)
{
UGameplayStatics::OpenLevel(GetWorld(), "/Game/AudioTestLevel", true, "listen");
}
}
void UFairiesGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnCreateSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
Sessions->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
if (bWasSuccessful)
{
OnStartSessionCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);
Sessions->StartSession(SessionName);
}
}
}
}
void UFairiesGameInstance::OnDestroySessionComplete(FName SessionName, bool bWasSuccessful)
{
UE_LOG(LogTemp, Warning, TEXT("In OnDestroySessionComplete"));
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
Sessions->ClearOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegateHandle);
if (bWasSuccessful)
{
UGameplayStatics::OpenLevel(GetWorld(), "/Game/MainLevel", true);
}
}
}
}
void UFairiesGameInstance::InitSession()
{
UE_LOG(LogTemp, Warning, TEXT("In InitSession"));
ULocalPlayer* const Player = GetFirstGamePlayer();
if (Player != nullptr) {
HostSession(Player->GetPreferredUniqueNetId(), FName("Game"), true, true, 1);
}
else {
UE_LOG(LogTemp, Warning, TEXT("Init: Player is null"));
}
}
void UFairiesGameInstance::Shutdown()
{
UE_LOG(LogTemp, Warning, TEXT("I am shutting down now"));
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
Sessions->DestroySession(FName("Game"), OnDestroySessionCompleteDelegate);
}
}
Super::Shutdown();
}
The log messages up to here indicate to me that the session is correctly created, and the game transitions to AudioTestLevel. However, two relevant warnings do come up, and I do not know if they are meaningful to my audio problem or not.
LogOnline: Warning: OSS: AutoLogin missing AUTH_LOGIN=<login id>.
LogOnline: Warning: OSS: No game present to join for session (GameSession)
The logic for capturing audio is handled in my player controller:
void AVoicedPlayerController::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("Custom player controller initialized"));
VoiceCapture = FVoiceModule::Get().CreateVoiceCapture();
bool Success = VoiceCapture->Start();
if (Success)
{
UE_LOG(LogTemp, Warning, TEXT("Voice capture started successfully"));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Voice capture not started successfully"));
}
}
void AVoicedPlayerController::Tick(float DeltaSeconds)
{
VoiceCaptureTick();
}
void AVoicedPlayerController::VoiceCaptureTick()
{
if (!VoiceCapture.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("Voice capture is not valid; skipping the rest of voice capture tick"));
return;
}
uint32 VoiceCaptureBytesAvailable;
EVoiceCaptureState::Type CaptureState = VoiceCapture->GetCaptureState(VoiceCaptureBytesAvailable);
UE_LOG(LogTemp, Warning, TEXT("Bytes available: %d
Capture state: %s"), VoiceCaptureBytesAvailable, EVoiceCaptureState::ToString(CaptureState));
VoiceCaptureBuffer.Reset();
if (CaptureState == EVoiceCaptureState::Ok && VoiceCaptureBytesAvailable > 0)
{
int16_t VoiceCaptureSample;
uint32 VoiceCaptureReadBytes;
float VoiceCaptureTotalSquared = 0;
VoiceCaptureBuffer.SetNumUninitialized(VoiceCaptureBytesAvailable);
VoiceCapture->GetVoiceData(VoiceCaptureBuffer.GetData(), VoiceCaptureBytesAvailable, VoiceCaptureReadBytes);
for (uint32 i = 0; i < (VoiceCaptureReadBytes / 2); i++)
{
VoiceCaptureSample = (VoiceCaptureBuffer[i * 2 + 1] << 8) | VoiceCaptureBuffer[i * 2];
VoiceCaptureTotalSquared += ((float)VoiceCaptureSample * (float)VoiceCaptureSample);
}
float VoiceCaptureMeanSquare = (2 * (VoiceCaptureTotalSquared / VoiceCaptureBuffer.Num()));
float VoiceCaptureRms = FMath::Sqrt(VoiceCaptureMeanSquare);
float VoiceCaptureFinalVolume = ((VoiceCaptureRms / 32768.0) * 200.f);
VoiceCaptureVolume = VoiceCaptureFinalVolume;
}
}
The logs indicate that there are always zero bytes available and that the state of audio capture is NoData, and hence the main audio processing block is never entered.
There are some errors that come up from the voice system, but I cannot discern what to do about them. Prior to the session’s initialization by the game instance, and again before my player controller is initialized in AudioTestLevel, I get this error:
LogVoiceEngine: Error: StopLocalVoiceProcessing: Ignoring stop request for non-owning user
Also, in the middle of session initialization (between the “In InitSession” and “Session created successfully” logs), I also receive this message:
LogVoiceEngine: Error: StartLocalVoiceProcessing(): Device is currently owned by another user
As I mentioned, I feel that I am at the edge of my understanding, so I am not really sure what to try next. I hoped someone might see something here that could nudge my team in the right direction.
For the sake of completeness, here is the log output.
LogPlayLevel: Creating play world package: /Game/UEDPIE_0_AUdioMainLevel
LogPlayLevel: PIE: StaticDuplicateObject took: (0.001694s)
LogAIModule: Creating AISystem for world AudioMainLevel
LogPlayLevel: PIE: World Init took: (0.000684s)
LogPlayLevel: PIE: Created PIE world by copying editor world from /Game/AUdioMainLevel.AudioMainLevel to /Game/UEDPIE_0_AUdioMainLevel.AudioMainLevel (0.002633s)
LogInit: XAudio2 using 'Speakers (High Definition Audio Device)' : 2 channels at 44.1 kHz using 16 bits per sample (channel mask 0x3)
LogInit: FAudioDevice initialized.
LogLoad: Game class is 'BP_AudioMainLevelGameMode_C'
LogWorld: Bringing World /Game/UEDPIE_0_AUdioMainLevel.AudioMainLevel up for play (max tick rate 0) at 2018.02.20-07.28.58
LogWorld: Bringing up level for play took: 0.000452
LogVoiceEncode: Display: EncoderVersion: libopus 1.1-beta
LogVoiceEngine: Error: StopLocalVoiceProcessing: Ignoring stop request for non-owning user
LogContentBrowser: Native class hierarchy updated for 'MovieSceneCapture' in 0.0005 seconds. Added 11 classes and 0 folders.
LogTemp: Warning: In InitSession
LogTemp: Warning: In HostSession
LogNetVersion: Fairies 1.0.0.0, NetCL: 3709383, EngineNetVer: 2, GameNetVer: 0 (Checksum: 1087104342)
LogInit: WinSock: I am Sanctum (10.220.5.72:0)
LogVoiceEncode: Display: EncoderVersion: libopus 1.1-beta
LogVoiceEngine: Error: StartLocalVoiceProcessing(): Device is currently owned by another user
LogTemp: Warning: Session created successfully
PIE: Play in editor start time for /Game/UEDPIE_0_AUdioMainLevel -0.22
LogBlueprintUserMessages: Late PlayInEditor Detection: Level '/Game/AUdioMainLevel.AudioMainLevel:PersistentLevel' has LevelScriptBlueprint '/Game/AUdioMainLevel.AudioMainLevel:PersistentLevel.AUdioMainLevel' with GeneratedClass '/Game/AUdioMainLevel.AUdioMainLevel_C' with ClassGeneratedBy '/Game/AUdioMainLevel.AudioMainLevel:PersistentLevel.AUdioMainLevel'
LogNet: Browse: /Game/AudioTestLevel?listen
LogLoad: LoadMap: /Game/AudioTestLevel?listen
LogAIModule: Creating AISystem for world AudioTestLevel
LogLoad: Game class is 'BP_AudioTestGameMode_C'
LogTemp: Display: ParseSettings for GameNetDriver
LogTemp: Display: ParseSettings for IpNetDriver_0
LogTemp: Display: ParseSettings for GameNetDriver
LogInit: WinSock: Socket queue 131072 / 131072
PacketHandlerLog: Loaded PacketHandler component: Engine.EngineHandlerComponentFactory (StatelessConnectHandlerComponent)
LogNet: GameNetDriver IpNetDriver_0 IpNetDriver listening on port 7777
LogWorld: Bringing World /Game/UEDPIE_0_AudioTestLevel.AudioTestLevel up for play (max tick rate 0) at 2018.02.20-07.28.58
LogOnline: Warning: OSS: AutoLogin missing AUTH_LOGIN=<login id>.
LogWorld: Bringing up level for play took: 0.000694
LogOnline: Warning: OSS: No game present to join for session (GameSession)
LogGameMode: FindPlayerStart: PATHS NOT DEFINED or NO PLAYERSTART with positive rating
LogVoiceEngine: Error: StopLocalVoiceProcessing: Ignoring stop request for non-owning user
LogTemp: Warning: Custom player controller initialized
LogTemp: Warning: Voice capture started successfully
LogLoad: Took 0.291920 seconds to LoadMap(/Game/AudioTestLevel)
LogTemp: Warning: Bytes available: 0
Capture state: No Data
...
LogOnline: Warning: OSS: No game present to leave for session (GameSession)
LogTemp: Warning: I am shutting down now
LogTemp: Warning: In OnDestroySessionComplete
LogOnline: Display: OSS: FOnlineSubsystemNull::Shutdown()
LogOnline: Display: OSS: FOnlineAsyncTaskManager::Stop() ActiveTask:0000000000000000 Tasks[0/0]
LogOnline: Display: OSS: FOnlineAsyncTaskManager::Exit() started
LogOnline: Display: OSS: FOnlineAsyncTaskManager::Exit() finished
LogPlayLevel: Display: Shutting down PIE online subsystems
LogBlueprintUserMessages: Late EndPlayMap Detection: Level '/Game/AUdioMainLevel.AudioMainLevel:PersistentLevel' has LevelScriptBlueprint '/Game/AUdioMainLevel.AudioMainLevel:PersistentLevel.AUdioMainLevel' with GeneratedClass '/Game/AUdioMainLevel.AUdioMainLevel_C' with ClassGeneratedBy '/Game/AUdioMainLevel.AudioMainLevel:PersistentLevel.AUdioMainLevel'
LogExit: GameNetDriver None shut down
LogPlayLevel: Display: Destroying online subsystem :Context_1
LogOnline: Display: OSS: FOnlineSubsystemNull::Shutdown()