目录
[ue5.1 web socket server:](#ue5.1 web socket server:)
[web socket server代码:](#web socket server代码:)
ue5.1 web socket server:
github地址:
https://github.com/h2ogit/UE5-ServerWebSocket/tree/main/ServerWebSocket
https://github.com/h2ogit/UE5-ServerWebSocketLite
代码修改:
cpp
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this](float DeltaTime)
//TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([=](float Time)
web socket server代码:
Actor.h
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "INetWebSocket.h"
//#include "WebSocketServer.h"
#include "ServerWebSocket.h"
#include "Containers/Ticker.h"
#include "Tickable.h"
#include "MyObject.generated.h"
UCLASS(BlueprintType, Blueprintable)
class METAHUMANCHARACTERHEIXI_API AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor()
{
// 启用 Tick
PrimaryActorTick.bCanEverTick = true;
}
UFUNCTION(BlueprintCallable, Category = "Demo")
void Init();
UFUNCTION(BlueprintCallable, Category = "Demo")
void Close()
{
ServerSocket.Reset();
ConnectedClients.Empty();
}
void HandleJsonMessage(const FString& JsonStr);
protected:
/** Actor Tick,每帧调用 */
virtual void Tick(float DeltaTime) override
{
Super::Tick(DeltaTime);
if (ServerSocket.IsValid())
{
ServerSocket->Tick();
}
}
public:
TSharedPtr<FServerWebSocket> ServerSocket;
TArray<TSharedPtr<INetWebSocket>> ConnectedClients;
UFUNCTION(BlueprintCallable, Category = "Demo")
void HelloWorld();
void SaveWav(const FString& FilePath, const TArray<uint8>& AudioBytes, int32 SampleRate, int32 NumChannels);
//
int32 FileIndex = 0;
};
Actor.cpp
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyObject.h"
#include "Engine/Engine.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Async/Async.h"
#include "Json.h"
#include "JsonUtilities.h"
#include "Misc/Base64.h"
void AMyActor::Init() {
ServerSocket = MakeShared<FServerWebSocket>();
bool bStarted = ServerSocket->Init(8080, FNetWebSocketClientConnectedCallBack::CreateLambda([this](INetWebSocket* NewClient)
{
UE_LOG(LogTemp, Log, TEXT("New client connected"));
TSharedPtr<INetWebSocket> ClientPtr = MakeShareable(NewClient);
ConnectedClients.Add(ClientPtr);
NewClient->SetReceiveCallBack(FNetWebSocketPacketReceivedCallBack::CreateLambda([this, ClientPtr](void* Data, int32 Size)
{
// 转成 uint8* 或 char* 用
const uint8* Bytes = reinterpret_cast<const uint8*>(Data);
FUTF8ToTCHAR Converter((const ANSICHAR*)Bytes, Size);
FString JsonStr(Converter.Length(), Converter.Get());
//FString JsonStr = FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(Bytes)));
//UE_LOG(LogTemp, Error, TEXT("Received message from client: %s"), *Msg);
UE_LOG(LogTemp, Log, TEXT("Recv message JSON size=%d"), Size);
HandleJsonMessage(JsonStr);
// 回发给客户端
//ClientPtr->Send(Bytes, Size);
}));
}));
if (!bStarted)
{
UE_LOG(LogTemp, Error, TEXT("Failed to start WebSocket server"));
}
}
void AMyActor::HandleJsonMessage(const FString& JsonStr)
{
TSharedPtr<FJsonObject> JsonObj;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonStr);
if (!FJsonSerializer::Deserialize(Reader, JsonObj) || !JsonObj.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("Invalid JSON"));
return;
}
FString Action = JsonObj->GetStringField(TEXT("action"));
if (Action != TEXT("synthesize"))
{
UE_LOG(LogTemp, Warning, TEXT("Unknown action: %s"), *Action);
return;
}
// 取 base64
FString Base64Audio = JsonObj->GetStringField(TEXT("audio"));
//int32 ChunkId = JsonObj->GetIntegerField(TEXT("chunk_id"));
TArray<uint8> AudioBytes;
if (!FBase64::Decode(Base64Audio, AudioBytes))
{
UE_LOG(LogTemp, Error, TEXT("Base64 decode failed"));
return;
}
// 保存路径
FString SavePath = FPaths::ProjectSavedDir() /
FString::Printf(TEXT("recev_%d.wav"), FileIndex++);
if (FFileHelper::SaveArrayToFile(AudioBytes, *SavePath))
{
UE_LOG(LogTemp, Log, TEXT("Saved wav: %s (%d bytes)"),
*SavePath, AudioBytes.Num());
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to save wav"));
}
}
void AMyActor::SaveWav(const FString& FilePath, const TArray<uint8>& AudioBytes, int32 SampleRate, int32 NumChannels)
{
if (AudioBytes.Num() == 0) return;
const int32 BitsPerSample = 16;
const int32 BlockAlign = NumChannels * BitsPerSample / 8;
const int32 ByteRate = SampleRate * BlockAlign;
const int32 DataSize = AudioBytes.Num();
const int32 ChunkSize = 36 + DataSize;
TArray<uint8> Wav;
Wav.Reserve(44 + DataSize); // 头44字节 + PCM数据
auto AppendInt32 = [&Wav](int32 V)
{
Wav.Append(reinterpret_cast<uint8*>(&V), sizeof(int32));
};
auto AppendInt16 = [&Wav](int16 V)
{
Wav.Append(reinterpret_cast<uint8*>(&V), sizeof(int16));
};
// --- WAV Header ---
Wav.Append(reinterpret_cast<const uint8*>("RIFF"), 4);
AppendInt32(ChunkSize);
Wav.Append(reinterpret_cast<const uint8*>("WAVE"), 4);
Wav.Append(reinterpret_cast<const uint8*>("fmt "), 4);
AppendInt32(16); // fmt chunk size
AppendInt16(1); // PCM format
AppendInt16(NumChannels);
AppendInt32(SampleRate);
AppendInt32(ByteRate);
AppendInt16(BlockAlign);
AppendInt16(BitsPerSample);
Wav.Append(reinterpret_cast<const uint8*>("data"), 4);
AppendInt32(DataSize);
// --- PCM Data ---
Wav.Append(AudioBytes);
FFileHelper::SaveArrayToFile(Wav, *FilePath);
}
inline void SaveWavToFile(const FString& FilePath, const TArray<uint8>& AudioBytes, int32 SampleRate, int32 NumChannels)
{
if (AudioBytes.Num() == 0) return;
const int32 BitsPerSample = 16;
const int32 BlockAlign = NumChannels * BitsPerSample / 8;
const int32 ByteRate = SampleRate * BlockAlign;
const int32 DataSize = AudioBytes.Num();
const int32 ChunkSize = 36 + DataSize;
TArray<uint8> Wav;
Wav.Reserve(44 + DataSize); // 头44字节 + PCM数据
auto AppendInt32 = [&Wav](int32 V) { Wav.Append(reinterpret_cast<uint8*>(&V), sizeof(int32)); };
auto AppendInt16 = [&Wav](int16 V) { Wav.Append(reinterpret_cast<uint8*>(&V), sizeof(int16)); };
// --- WAV Header ---
Wav.Append(reinterpret_cast<const uint8*>("RIFF"), 4);
AppendInt32(ChunkSize);
Wav.Append(reinterpret_cast<const uint8*>("WAVE"), 4);
Wav.Append(reinterpret_cast<const uint8*>("fmt "), 4);
AppendInt32(16); // fmt chunk size
AppendInt16(1); // PCM format
AppendInt16(NumChannels);
AppendInt32(SampleRate);
AppendInt32(ByteRate);
AppendInt16(BlockAlign);
AppendInt16(BitsPerSample);
Wav.Append(reinterpret_cast<const uint8*>("data"), 4);
AppendInt32(DataSize);
// --- PCM Data ---
Wav.Append(AudioBytes);
FFileHelper::SaveArrayToFile(Wav, *FilePath);
}
//void AMyActor::Tick(float DeltaTime)
//{
// //Super::Tick(DeltaTime);
//
// if (ServerSocket.IsValid())
// {
// ServerSocket->Tick(); // 处理网络事件
// }
//}
void AMyActor::HelloWorld()
{
UE_LOG(LogTemp, Warning, TEXT("Hello World from AMyActor!"));
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("Hello World from AMyActor!"));
}
ServerSocket = MakeShared<FServerWebSocket>();
bool bStarted = ServerSocket->Init(8080,
FNetWebSocketClientConnectedCallBack::CreateLambda([this](INetWebSocket* NewClient)
{
// 每个新连接客户端都可以包装成 TSharedPtr
TSharedPtr<INetWebSocket> ClientPtr = MakeShareable(NewClient);
// 保存到你的客户端列表
ConnectedClients.Add(ClientPtr);
// 通知封装类或蓝图
AsyncTask(ENamedThreads::GameThread, [this, ClientPtr]()
{
//WebSocketServerWrapper->OnConnected.ExecuteIfBound(ClientPtr);
});
// 给客户端绑定消息回调
ClientPtr->SetReceiveCallBack(FNetWebSocketPacketReceivedCallBack::CreateLambda(
[this, ClientPtr](void* Data, int32 Size)
{
TArray<uint8> Buffer;
Buffer.Append(reinterpret_cast<const uint8*>(Data), Size);
// 切到 GameThread 执行
AsyncTask(ENamedThreads::GameThread, [this, ClientPtr, Buffer = MoveTemp(Buffer)]() mutable
{
//WebSocketServerWrapper->OnMessageFromClient.ExecuteIfBound(ClientPtr, Buffer);
//OnMessage.ExecuteIfBound(ClientPtr, Buffer);
});
}
));
// 可以在这里触发 OnConnected 委托
//AsyncTask(ENamedThreads::GameThread, [this, ClientPtr]()
// {
// //OnConnected.ExecuteIfBound(ClientPtr);
// });
})
);
//GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this]()
// {
// if (ServerSocket.IsValid())
// {
// ServerSocket->Tick();
// }
// }, 0.01f, true); // 每 0.01s 调一次
}