diff --git a/Unreal/PS_AI_Agent/Config/DefaultEngine.ini b/Unreal/PS_AI_Agent/Config/DefaultEngine.ini index 305abfa..29cdec9 100644 --- a/Unreal/PS_AI_Agent/Config/DefaultEngine.ini +++ b/Unreal/PS_AI_Agent/Config/DefaultEngine.ini @@ -153,6 +153,12 @@ FontDPI=72 +EnumRedirects=(OldName="EPS_AI_Agent_Emotion", NewName="EPS_AI_ConvAgent_Emotion") +EnumRedirects=(OldName="EPS_AI_Agent_EmotionIntensity", NewName="EPS_AI_ConvAgent_EmotionIntensity") +[OnlineSubsystem] +DefaultPlatformService=Null + +[OnlineSubsystemNull] +bEnabled=true + [/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] bEnablePlugin=True bAllowNetworkConnection=True diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp index a42ad9c..e41a9f0 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_ElevenLabsComponent.cpp @@ -637,46 +637,35 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::HandleAudioReceived(const TArray(QueueBefore) / 16000.0f) * 1000.0f); } - // Network: Opus-compress and broadcast to all clients before local playback. - if (GetOwnerRole() != ROLE_Authority || !OpusEncoder.IsValid()) + // Network: broadcast audio to all clients. + if (GetOwnerRole() == ROLE_Authority) { - static bool bWarnedOnce = false; - if (!bWarnedOnce) + if (OpusEncoder.IsValid()) { - bWarnedOnce = true; - UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Warning, - TEXT("[NET-SRV] Cannot multicast audio! Role=%d (need %d=Authority), OpusEncoder=%s, FVoiceModule=%s"), - static_cast(GetOwnerRole()), static_cast(ROLE_Authority), - OpusEncoder.IsValid() ? TEXT("VALID") : TEXT("NULL"), - FVoiceModule::IsAvailable() ? TEXT("available") : TEXT("UNAVAILABLE")); - } - } - if (GetOwnerRole() == ROLE_Authority && OpusEncoder.IsValid()) - { - uint32 CompressedSize = static_cast(OpusWorkBuffer.Num()); - int32 Remainder = OpusEncoder->Encode(PCMData.GetData(), PCMData.Num(), - OpusWorkBuffer.GetData(), CompressedSize); + // Opus path: compress then send. + uint32 CompressedSize = static_cast(OpusWorkBuffer.Num()); + OpusEncoder->Encode(PCMData.GetData(), PCMData.Num(), + OpusWorkBuffer.GetData(), CompressedSize); - if (CompressedSize > 0) - { - TArray CompressedData; - CompressedData.Append(OpusWorkBuffer.GetData(), CompressedSize); - - if (bDebug) + if (CompressedSize > 0) { - UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log, - TEXT("[NET-SRV] Multicasting Opus audio: %d bytes PCM → %d bytes Opus (%.1f:1 ratio)"), - PCMData.Num(), CompressedSize, - PCMData.Num() > 0 ? static_cast(PCMData.Num()) / CompressedSize : 0.f); + TArray CompressedData; + CompressedData.Append(OpusWorkBuffer.GetData(), CompressedSize); + MulticastReceiveAgentAudio(CompressedData); } - - MulticastReceiveAgentAudio(CompressedData); } else { - UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Warning, - TEXT("[NET-SRV] Opus encode produced 0 bytes from %d bytes PCM — audio not sent."), - PCMData.Num()); + // Fallback: send raw PCM (no compression). ~32 KB/s at 16kHz 16-bit mono. + // Fine for LAN; revisit with proper Opus if internet play is needed. + static bool bWarnedOnce = false; + if (!bWarnedOnce) + { + bWarnedOnce = true; + UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Warning, + TEXT("[NET-SRV] Opus encoder unavailable — sending raw PCM (no compression).")); + } + MulticastReceiveAgentAudio(PCMData); } } @@ -1346,57 +1335,36 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::ClientConversationFailed_Implementati // Network: Multicast RPCs // ───────────────────────────────────────────────────────────────────────────── void UPS_AI_ConvAgent_ElevenLabsComponent::MulticastReceiveAgentAudio_Implementation( - const TArray& OpusData) + const TArray& AudioData) { // Server already handled playback in HandleAudioReceived. if (GetOwnerRole() == ROLE_Authority) return; - if (!OpusDecoder.IsValid()) - { - UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Warning, - TEXT("[NET] MulticastReceiveAgentAudio: OpusDecoder is INVALID — audio dropped. FVoiceModule available: %s"), - FVoiceModule::IsAvailable() ? TEXT("YES") : TEXT("NO")); - return; - } - // LOD: skip audio if too far (unless this client is the speaker). const float Dist = GetDistanceToLocalPlayer(); const bool bIsSpeaker = IsLocalPlayerConversating(); if (!bIsSpeaker && AudioLODCullDistance > 0.f && Dist > AudioLODCullDistance) { - if (bDebug && DebugVerbosity >= 2) - { - UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log, - TEXT("[NET] MulticastReceiveAgentAudio: LOD culled (dist=%.0f > cull=%.0f)"), - Dist, AudioLODCullDistance); - } return; } - // Decode Opus → PCM. - const uint32 MaxDecompressedSize = 16000 * 2; // 1 second of 16kHz 16-bit mono TArray PCMBuffer; - PCMBuffer.SetNumUninitialized(MaxDecompressedSize); - uint32 DecompressedSize = MaxDecompressedSize; - OpusDecoder->Decode(OpusData.GetData(), OpusData.Num(), - PCMBuffer.GetData(), DecompressedSize); - if (DecompressedSize == 0) + if (OpusDecoder.IsValid()) { - UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Warning, - TEXT("[NET] MulticastReceiveAgentAudio: Opus decode failed (0 bytes output from %d bytes input)"), - OpusData.Num()); - return; + // Opus path: decode compressed audio. + const uint32 MaxDecompressedSize = 16000 * 2; + PCMBuffer.SetNumUninitialized(MaxDecompressedSize); + uint32 DecompressedSize = MaxDecompressedSize; + OpusDecoder->Decode(AudioData.GetData(), AudioData.Num(), + PCMBuffer.GetData(), DecompressedSize); + if (DecompressedSize == 0) return; + PCMBuffer.SetNum(DecompressedSize); } - PCMBuffer.SetNum(DecompressedSize); - - if (bDebug) + else { - UE_LOG(LogPS_AI_ConvAgent_ElevenLabs, Log, - TEXT("[NET] MulticastReceiveAgentAudio: decoded %d bytes Opus → %d bytes PCM | bAgentSpeaking=%s | AudioComp playing=%s"), - OpusData.Num(), DecompressedSize, - bAgentSpeaking ? TEXT("true") : TEXT("false"), - (AudioPlaybackComponent && AudioPlaybackComponent->IsPlaying()) ? TEXT("true") : TEXT("false")); + // Fallback: data is already raw PCM (sent uncompressed by server). + PCMBuffer = AudioData; } // Local playback. diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h index 1f1682f..d180eab 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_ElevenLabsComponent.h @@ -339,9 +339,9 @@ public: UFUNCTION(Server, Reliable) void ServerRequestInterrupt(); - /** Broadcast Opus-compressed agent audio to all clients. */ - UFUNCTION(NetMulticast, Unreliable) - void MulticastReceiveAgentAudio(const TArray& OpusData); + /** Broadcast agent audio to all clients (Opus-compressed or raw PCM fallback). */ + UFUNCTION(NetMulticast, Reliable) + void MulticastReceiveAgentAudio(const TArray& AudioData); /** Notify all clients that the agent started speaking (first audio chunk). */ UFUNCTION(NetMulticast, Reliable)