Compare commits
No commits in common. "1c4dbfc402ab2a1192c616f2eb9f26d34afae33d" and "5e18e7cc8c8ccac729c7606d0223db730e92f734" have entirely different histories.
1c4dbfc402
...
5e18e7cc8c
@ -630,26 +630,7 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::FeedExternalAudio(const TArray<float>
|
|||||||
// Route through relay (clients can't call Server RPCs on NPC actors).
|
// Route through relay (clients can't call Server RPCs on NPC actors).
|
||||||
if (auto* Relay = FindLocalRelayComponent())
|
if (auto* Relay = FindLocalRelayComponent())
|
||||||
{
|
{
|
||||||
// Opus-compress mic audio before sending over the network.
|
Relay->ServerRelayMicAudio(GetOwner(), MicAccumulationBuffer);
|
||||||
// 3200 bytes raw PCM → ~200 bytes Opus (~16x reduction).
|
|
||||||
if (OpusEncoder.IsValid())
|
|
||||||
{
|
|
||||||
uint32 CompressedSize = static_cast<uint32>(OpusWorkBuffer.Num());
|
|
||||||
OpusEncoder->Encode(MicAccumulationBuffer.GetData(),
|
|
||||||
MicAccumulationBuffer.Num(),
|
|
||||||
OpusWorkBuffer.GetData(), CompressedSize);
|
|
||||||
if (CompressedSize > 0)
|
|
||||||
{
|
|
||||||
TArray<uint8> Compressed;
|
|
||||||
Compressed.Append(OpusWorkBuffer.GetData(), CompressedSize);
|
|
||||||
Relay->ServerRelayMicAudio(GetOwner(), Compressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Fallback: raw PCM (no Opus encoder available).
|
|
||||||
Relay->ServerRelayMicAudio(GetOwner(), MicAccumulationBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -660,33 +641,6 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::FeedExternalAudio(const TArray<float>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Network audio helpers (used by InteractionComponent relay)
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
bool UPS_AI_ConvAgent_ElevenLabsComponent::DecompressMicAudio(
|
|
||||||
const TArray<uint8>& CompressedData, TArray<uint8>& OutPCM) const
|
|
||||||
{
|
|
||||||
if (!OpusDecoder.IsValid() || CompressedData.Num() >= GetMicChunkMinBytes())
|
|
||||||
{
|
|
||||||
return false; // Not Opus-compressed or no decoder.
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32 MaxDecoded = 16000 * 2; // 1 sec of 16kHz 16-bit mono
|
|
||||||
OutPCM.SetNumUninitialized(MaxDecoded);
|
|
||||||
uint32 DecodedSize = MaxDecoded;
|
|
||||||
OpusDecoder->Decode(CompressedData.GetData(), CompressedData.Num(),
|
|
||||||
OutPCM.GetData(), DecodedSize);
|
|
||||||
|
|
||||||
if (DecodedSize == 0) return false;
|
|
||||||
OutPCM.SetNum(DecodedSize, EAllowShrinking::No);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 UPS_AI_ConvAgent_ElevenLabsComponent::GetMicChunkMinBytesPublic() const
|
|
||||||
{
|
|
||||||
return GetMicChunkMinBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// State queries
|
// State queries
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
@ -1359,26 +1313,9 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::OnMicrophoneDataCaptured(const TArray
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Route through relay (clients can't call Server RPCs on NPC actors).
|
// Route through relay (clients can't call Server RPCs on NPC actors).
|
||||||
// Opus-compress before sending — same logic as FeedExternalAudio.
|
|
||||||
if (auto* Relay = FindLocalRelayComponent())
|
if (auto* Relay = FindLocalRelayComponent())
|
||||||
{
|
{
|
||||||
if (OpusEncoder.IsValid())
|
Relay->ServerRelayMicAudio(GetOwner(), MicAccumulationBuffer);
|
||||||
{
|
|
||||||
uint32 CompressedSize = static_cast<uint32>(OpusWorkBuffer.Num());
|
|
||||||
OpusEncoder->Encode(MicAccumulationBuffer.GetData(),
|
|
||||||
MicAccumulationBuffer.Num(),
|
|
||||||
OpusWorkBuffer.GetData(), CompressedSize);
|
|
||||||
if (CompressedSize > 0)
|
|
||||||
{
|
|
||||||
TArray<uint8> Compressed;
|
|
||||||
Compressed.Append(OpusWorkBuffer.GetData(), CompressedSize);
|
|
||||||
Relay->ServerRelayMicAudio(GetOwner(), Compressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Relay->ServerRelayMicAudio(GetOwner(), MicAccumulationBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1717,16 +1654,14 @@ void UPS_AI_ConvAgent_ElevenLabsComponent::InitOpusCodec()
|
|||||||
FVoiceModule& VoiceModule = FVoiceModule::Get();
|
FVoiceModule& VoiceModule = FVoiceModule::Get();
|
||||||
|
|
||||||
const ENetRole Role = GetOwnerRole();
|
const ENetRole Role = GetOwnerRole();
|
||||||
|
if (Role == ROLE_Authority)
|
||||||
|
{
|
||||||
|
OpusEncoder = VoiceModule.CreateVoiceEncoder(
|
||||||
|
PS_AI_ConvAgent_Audio_ElevenLabs::SampleRate,
|
||||||
|
PS_AI_ConvAgent_Audio_ElevenLabs::Channels,
|
||||||
|
EAudioEncodeHint::VoiceEncode_Voice);
|
||||||
|
}
|
||||||
|
|
||||||
// Encoder: on Authority it encodes agent audio for multicast to clients.
|
|
||||||
// On clients it encodes mic audio for relay to server (~16x compression).
|
|
||||||
OpusEncoder = VoiceModule.CreateVoiceEncoder(
|
|
||||||
PS_AI_ConvAgent_Audio_ElevenLabs::SampleRate,
|
|
||||||
PS_AI_ConvAgent_Audio_ElevenLabs::Channels,
|
|
||||||
EAudioEncodeHint::VoiceEncode_Voice);
|
|
||||||
|
|
||||||
// Decoder: on clients it decodes agent audio from multicast.
|
|
||||||
// On Authority it decodes mic audio arriving via relay from clients.
|
|
||||||
OpusDecoder = VoiceModule.CreateVoiceDecoder(
|
OpusDecoder = VoiceModule.CreateVoiceDecoder(
|
||||||
PS_AI_ConvAgent_Audio_ElevenLabs::SampleRate,
|
PS_AI_ConvAgent_Audio_ElevenLabs::SampleRate,
|
||||||
PS_AI_ConvAgent_Audio_ElevenLabs::Channels);
|
PS_AI_ConvAgent_Audio_ElevenLabs::Channels);
|
||||||
|
|||||||
@ -603,25 +603,13 @@ void UPS_AI_ConvAgent_InteractionComponent::ServerRelayEndConversation_Implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UPS_AI_ConvAgent_InteractionComponent::ServerRelayMicAudio_Implementation(
|
void UPS_AI_ConvAgent_InteractionComponent::ServerRelayMicAudio_Implementation(
|
||||||
AActor* AgentActor, const TArray<uint8>& AudioBytes)
|
AActor* AgentActor, const TArray<uint8>& PCMBytes)
|
||||||
{
|
{
|
||||||
if (!AgentActor) return;
|
if (!AgentActor) return;
|
||||||
auto* Agent = AgentActor->FindComponentByClass<UPS_AI_ConvAgent_ElevenLabsComponent>();
|
auto* Agent = AgentActor->FindComponentByClass<UPS_AI_ConvAgent_ElevenLabsComponent>();
|
||||||
if (!Agent) return;
|
if (!Agent) return;
|
||||||
|
|
||||||
// Clients Opus-encode mic audio before sending via relay (~200 bytes
|
Agent->ServerSendMicAudio_Implementation(PCMBytes);
|
||||||
// instead of 3200 bytes per 100ms chunk). Decode back to raw PCM here
|
|
||||||
// before forwarding to the WebSocket which expects uncompressed int16.
|
|
||||||
TArray<uint8> DecodedPCM;
|
|
||||||
if (Agent->DecompressMicAudio(AudioBytes, DecodedPCM))
|
|
||||||
{
|
|
||||||
Agent->ServerSendMicAudio_Implementation(DecodedPCM);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Raw PCM fallback (no Opus or data is already uncompressed).
|
|
||||||
Agent->ServerSendMicAudio_Implementation(AudioBytes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UPS_AI_ConvAgent_InteractionComponent::ServerRelaySendText_Implementation(
|
void UPS_AI_ConvAgent_InteractionComponent::ServerRelaySendText_Implementation(
|
||||||
|
|||||||
@ -460,16 +460,6 @@ public:
|
|||||||
FActorComponentTickFunction* ThisTickFunction) override;
|
FActorComponentTickFunction* ThisTickFunction) override;
|
||||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||||
|
|
||||||
// ── Network audio helpers (used by InteractionComponent relay) ────────
|
|
||||||
|
|
||||||
/** Decompress Opus-encoded mic audio back to raw PCM.
|
|
||||||
* Returns true and fills OutPCM on success; returns false if no Opus
|
|
||||||
* decoder is available or the data doesn't look compressed. */
|
|
||||||
bool DecompressMicAudio(const TArray<uint8>& CompressedData, TArray<uint8>& OutPCM) const;
|
|
||||||
|
|
||||||
/** Minimum raw PCM bytes expected per mic chunk (used to detect Opus vs raw). */
|
|
||||||
int32 GetMicChunkMinBytesPublic() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// ── Network OnRep handlers ───────────────────────────────────────────────
|
// ── Network OnRep handlers ───────────────────────────────────────────────
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
@ -612,8 +602,8 @@ private:
|
|||||||
int32 GetMicChunkMinBytes() const { return MicChunkDurationMs * 32; }
|
int32 GetMicChunkMinBytes() const { return MicChunkDurationMs * 32; }
|
||||||
|
|
||||||
// ── Opus codec (network audio compression) ───────────────────────────────
|
// ── Opus codec (network audio compression) ───────────────────────────────
|
||||||
TSharedPtr<IVoiceEncoder> OpusEncoder; // All: server encodes agent audio, clients encode mic audio
|
TSharedPtr<IVoiceEncoder> OpusEncoder; // Server only
|
||||||
TSharedPtr<IVoiceDecoder> OpusDecoder; // All: clients decode agent audio, server decodes mic audio
|
TSharedPtr<IVoiceDecoder> OpusDecoder; // All clients
|
||||||
TArray<uint8> OpusWorkBuffer; // Reusable scratch buffer for encode/decode
|
TArray<uint8> OpusWorkBuffer; // Reusable scratch buffer for encode/decode
|
||||||
|
|
||||||
void InitOpusCodec();
|
void InitOpusCodec();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user