One wall came up again and not been able to solve it.
I have two classes.
.H
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "SFML/Audio.hpp"
#include <SFML/Network.hpp>
#include <list>
#include "HAL/ThreadSafeBool.h"
#include "Public/TimerManager.h"
#include "Engine/World.h"
#include "sfmlServer.generated.h"
////////////////////////////////////////////////////////////
/// Customized sound stream for acquiring audio data
/// from the network
////////////////////////////////////////////////////////////
class NetworkAudioStream : public sf::SoundStream
{
public:
const sf::Uint8 audioData = 1;
const sf::Uint8 endOfStream = 2;
////////////////////////////////////////////////////////////
/// Default constructor
///
////////////////////////////////////////////////////////////
NetworkAudioStream() :
m_offset(0),
m_hasFinished(false)
{
// Set the sound parameters
initialize(1, 44100);
}
////////////////////////////////////////////////////////////
/// Run the server, stream audio data from the client
///
////////////////////////////////////////////////////////////
void start(unsigned short port)
{
// Listen to the given port for incoming connections
if (m_listener.listen(port) != sf::Socket::Done)
return;
UE_LOG(LogTemp, Warning, TEXT("Server Started at port %i"), port);
selector.add(m_listener);
// Start playback
play();
// Start receiving audio data
receiveLoop();
}
private:
////////////////////////////////////////////////////////////
/// /see SoundStream::OnGetData
///
////////////////////////////////////////////////////////////
virtual bool onGetData(sf::SoundStream::Chunk& data)
{
// We have reached the end of the buffer and all audio data have been played: we can stop playback
if ((m_offset >= m_samples.size()) && m_hasFinished)
return false;
// No new data has arrived since last update: wait until we get some
while ((m_offset >= m_samples.size()) && !m_hasFinished)
sf::sleep(sf::milliseconds(10));
// Copy samples into a local buffer to avoid synchronization problems
// (don't forget that we run in two separate threads)
{
sf::Lock lock(m_mutex);
m_tempBuffer.assign(m_samples.begin() + m_offset, m_samples.end());
}
// Fill audio data to pass to the stream
data.samples = &m_tempBuffer[0];
data.sampleCount = m_tempBuffer.size();
// Update the playing offset
m_offset += m_tempBuffer.size();
UE_LOG(LogTemp, Warning, TEXT("Getting Data"));
return true;
}
////////////////////////////////////////////////////////////
/// /see SoundStream::OnSeek
///
////////////////////////////////////////////////////////////
virtual void onSeek(sf::Time timeOffset)
{
m_offset = timeOffset.asMilliseconds() * getSampleRate() * getChannelCount() / 1000;
}
////////////////////////////////////////////////////////////
/// Get audio data from the client until playback is stopped
///
////////////////////////////////////////////////////////////
void receiveLoop()
{
while (Canrun)
{
// Make the selector wait for data on any socket
if (selector.wait())
{
// Test the listener
if (selector.isReady(m_listener))
{
// The listener is ready: there is a pending connection
sf::TcpSocket* client = new sf::TcpSocket;
if (m_listener.accept(*client) == sf::Socket::Done)
{
// Add the new client to the clients list
clients.push_back(client);
// Add the new client to the selector so that we will
// be notified when he sends something
selector.add(*client);
UE_LOG(LogTemp, Warning, TEXT("New Client Joined to chat"));
}
else
{
// Error, we won't get a new connection, delete the socket
delete client;
}
}
else
{
// The listener socket is not ready, test all other sockets (the clients)
for (std::list<sf::TcpSocket*>::iterator it = clients.begin(); it != clients.end(); ++it)
{
sf::TcpSocket& client = **it;
if (selector.isReady(client))
{
// The client has sent some data, we can receive it
sf::Packet packet;
if (client.receive(packet) == sf::Socket::Done)
{
// Get waiting audio data from the network
//sf::Packet packet;
//if (client.receive(packet) != sf::Socket::Done)
// break;
// Extract the message ID
sf::Uint8 id;
packet >> id;
if (id == endOfStream)
{
// End of stream reached: we stop receiving audio data
// std::cout << "Audio data has been 100% received!" << std::endl;
UE_LOG(LogTemp, Warning, TEXT("Audio Stream successfully finished"));
stop();
Canrun = false;
}
else if (id == audioData)
{
// Extract audio samples from the packet, and append it to our samples buffer
const sf::Int16* samples = reinterpret_cast<const sf::Int16*>(static_cast<const char*>(packet.getData()) + 1);
std::size_t sampleCount = (packet.getDataSize() - 1) / sizeof(sf::Int16);
// Don't forget that the other thread can access the sample array at any time
// (so we protect any operation on it with the mutex)
{
sf::Lock lock(m_mutex);
std::copy(samples, samples + sampleCount, std::back_inserter(m_samples));
}
// Send to all clients.
for (std::list<sf::TcpSocket*>::iterator it = clients.begin(); it != clients.end(); ++it)
{
sf::TcpSocket& client2 = **it;
// Don't send same voice to speaker.
if (client2.getRemoteAddress() != client.getRemoteAddress())
{
client2.send(packet);
}
}
}
else
{
// Something's wrong...
UE_LOG(LogTemp, Warning, TEXT("Invalid packet received..."));
stop();
Canrun = false;
}
}
}
}
}
}
}
UE_LOG(LogTemp, Warning, TEXT("Exit While Loop"));
}
////////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
sf::TcpListener m_listener;
sf::TcpSocket m_client;
// Create a list to store the future clients
std::list<sf::TcpSocket*> clients;
// Create a selector
sf::SocketSelector selector;
sf::Mutex m_mutex;
std::vector<sf::Int16> m_samples;
std::vector<sf::Int16> m_tempBuffer;
std::size_t m_offset;
bool m_hasFinished;
public:
bool Canrun = true;
};
UCLASS()
class SFMLTEST_API UsfmlServer : public UObject
{
GENERATED_BODY()
public:
NetworkAudioStream audioStream;
UFUNCTION(BlueprintCallable)
void StartServer(int32 port);
UFUNCTION(BlueprintCallable)
void StopServer();
};
As you can see this class is responsible for listening clients and passing sound packets to everybody. Now hear me out, because this sounds so strange to me and I think there is some underlying thing that I don't know. I have problem by having this class to go full circle and closing.
I can start the Server in my project and Clients can join to it. If client leaves and sends the
endOfStream my server successfully completes and shuts down. All good so far.
Problem is that I want to shutdown server in my own computer. Logically I could simply change the Canrun to false and program would shutdown in next loop but that ain't happening. Program gets stuck to somewhere. I even created custom class and send the same
endOfStream message to server locally, but that ain't helping, the class just wont complete. I then tried to use the example VoIP in same computer where the server is running by connecting to localhost. After sending
endOfStream, the server shuts down propertly again.
Now comes the strange part. While I was debugging, I removed EVERYTHING from the receiveLoop() function and still the class wont complete. The only way I get it to complete is to connect to it from different program and send the end message. I have been fighting this for so long but cannot figure out what is causing this.
My .CPP file here:
This is my current attempt. But like I said, I had a system, that spawn a temporaly class that sends the EndOfStream Packet to server from the same computer, but that didn't help. I can see that server receives the packet. and says
UE_LOG(LogTemp, Warning, TEXT("Audio Stream successfully finished")); but the program wont end. Also, notice that I need to run this program in backgroundthread in Unrea or the mainthread freezes. Not sure if that has something to do with it.
// Fill out your copyright notice in the Description page of Project Settings.
#include "sfmlServer.h"
#include "LambdaRunnable.h"
FLambdaRunnable* LambdaServer;
////////////////////////////////////////////////////////////
/// Launch a server and wait for incoming audio data from
/// a connected client
///
////////////////////////////////////////////////////////////
void UsfmlServer::StartServer(int32 port)
{
LambdaServer = FLambdaRunnable::RunLambdaOnBackGroundThread([&]()
{
// Build an audio stream to play sound data as it is received through the network
audioStream.start(port);
// Loop until the sound playback is finished
while (audioStream.getStatus() != sf::SoundStream::Stopped)
{
// Leave some CPU time for other threads
sf::sleep(sf::milliseconds(100));
//Initial wait before starting
FPlatformProcess::Sleep(100);
}
});
}
void UsfmlServer::StopServer()
{
audioStream.Canrun = false;
}
I'm all out of hope here. What bugs me the most is WHY it's working as it supposed to when the data is send over the network, but not locally. And why the program doesn't complete, even if the whole function is empty. Please help.