Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【Server Core Audio Client On Win】【WIP】feat: add server core audio client for windows #134

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion server-core/CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"name": "windows-Debug",
"inherits": "windows-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
"CMAKE_BUILD_TYPE": "Debug",
"VCPKG_TARGET_TRIPLET": "x64-windows-static",
"CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreadedDebug"
}
},
{
Expand Down
5 changes: 5 additions & 0 deletions server-core/src/audio_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ class audio_manager : private detail::audio_manager_impl, public std::enable_sha
void start_loopback_recording(std::shared_ptr<network_manager> network_manager, const capture_config& config);
void stop();
void do_loopback_recording(std::shared_ptr<network_manager> network_manager, const capture_config& config);

void audio_init(AudioFormat& format);
void audio_start();
void audio_play(const std::vector<char>& buffer);
void audio_stop();

std::string get_format_binary();

Expand Down
15 changes: 15 additions & 0 deletions server-core/src/linux/audio_manager_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,4 +421,19 @@ std::string audio_manager::get_default_endpoint()
return user_data.default_id;
}

void audio_manager::audio_init(AudioFormat& format)
{
spdlog::error("not implement");
}

void audio_manager::audio_start()
{
spdlog::error("not implement");
}

void audio_manager::audio_play(const std::vector<char>& buffer)
{
spdlog::error("not implement");
}

#endif // linux
44 changes: 32 additions & 12 deletions server-core/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
#include "config.h"
#include "audio_manager.hpp"
#include "network_manager.hpp"

#include <cxxopts.hpp>
#include <iostream>
#include <spdlog/spdlog.h>

#include "config.h"
#include "audio_manager.hpp"
#include "network_manager.hpp"

using string = std::string;

std::pair<std::string, uint16_t> parse_host_port(const std::string& s) {
size_t pos = s.find(':');
std::string host = s.substr(0, pos);
uint16_t port;
if (pos == std::string::npos) {
port = 65530;
} else {
port = (uint16_t)std::stoi(s.substr(pos + 1));
}
return {host, port};
}

int main(int argc, char* argv[])
{
auto default_address = network_manager::get_default_address();
Expand All @@ -18,12 +30,14 @@ int main(int argc, char* argv[])
help_string += fmt::format(" {} --bind={} --encoding=f32 --channels=2 --sample-rate=48000\n", AUDIO_SHARE_BIN_NAME, default_address.empty() ? "192.168.3.2": default_address);
help_string += fmt::format(" {} -l\n", AUDIO_SHARE_BIN_NAME);
help_string += fmt::format(" {} --list-encoding\n", AUDIO_SHARE_BIN_NAME);
help_string += fmt::format(" {} --connect={}\n", AUDIO_SHARE_BIN_NAME, "192.168.3.2");
cxxopts::Options options(AUDIO_SHARE_BIN_NAME, help_string);

// clang-format off
options.add_options()
("h,help", "Print usage")
("l,list-endpoint", "List available endpoints")
("connect", "Connect to server", cxxopts::value<string>(), "[host][:<port>]")
("b,bind", "The server bind address. If not set, will use default", cxxopts::value<string>()->implicit_value(default_address), "[host][:<port>]")
("e,endpoint", "Specify the endpoint id. If not set or set \"default\", will use default", cxxopts::value<string>()->default_value("default"), "[endpoint]")
("encoding", "Specify the capture encoding. If not set or set \"default\", will use default", cxxopts::value<audio_manager::encoding_t>()->default_value("default"), "[encoding]")
Expand Down Expand Up @@ -82,14 +96,7 @@ int main(int argc, char* argv[])

if (result.count("bind")) {
auto s = result["bind"].as<string>();
size_t pos = s.find(':');
string host = s.substr(0, pos);
uint16_t port;
if (pos == string::npos) {
port = 65530;
} else {
port = (uint16_t)std::stoi(s.substr(pos + 1));
}
auto [host, port] = parse_host_port(s);

auto audio_manager = std::make_shared<class audio_manager>();

Expand All @@ -108,6 +115,19 @@ int main(int argc, char* argv[])
return EXIT_SUCCESS;
}

if (result.count("connect")) {
auto s = result["connect"].as<string>();
auto [host, port] = parse_host_port(s);

auto audio_manager = std::make_shared<class audio_manager>();
auto network_manager = std::make_shared<class network_manager>(audio_manager);

network_manager->start_client(host, port);
network_manager->wait_client();

return EXIT_SUCCESS;
}

// no arg
std::cerr << options.help();
} catch (const cxxopts::exceptions::exception& e) {
Expand Down
197 changes: 197 additions & 0 deletions server-core/src/network_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <coroutine>

#ifdef _WINDOWS
#define NOMINMAX
#include <iphlpapi.h>
#include <winsock2.h>
#include <ws2tcpip.h>
Expand All @@ -36,6 +37,7 @@
#endif

#include <spdlog/spdlog.h>
#include <spdlog/fmt/bin_to_hex.h>
#include <fmt/ranges.h>

namespace ip = asio::ip;
Expand Down Expand Up @@ -423,3 +425,198 @@ void network_manager::broadcast_audio_data(const char* data, size_t count, int b
}
});
}


void network_manager::start_client(const std::string& host, uint16_t port)
{
if (_ioc == nullptr) {
_ioc = std::make_shared<asio::io_context>();
}

auto task = [] (std::shared_ptr<network_manager> self, const std::string host, uint16_t port) -> void {
assert(self != nullptr && "self is a null pointer");
assert(self->_ioc != nullptr && "network_manager::_ioc is a null pointer");

try {
spdlog::info("connect to server {}:{}", host, port);
asio::co_spawn(*self->_ioc, self->client_connect(self, host, port), asio::detached);
auto work = asio::make_work_guard(*self->_ioc);
self->_ioc->run();
} catch (std::exception& e) {
spdlog::error("exception in io_context thread: {}", e.what());
}
};

try {
if (!_net_thread.joinable()) {
spdlog::info("start thread");
_net_thread = std::thread(task, shared_from_this(), host, port);
}
} catch (std::exception& e) {
spdlog::error("failed to start the network thread: {}", e.what());
}

spdlog::info("start client");
}

asio::awaitable<void> network_manager::client_heartbeat_loop(std::shared_ptr<tcp_socket> socket)
{
asio::steady_timer timer(*_ioc);

while (true) {
if (!is_running()) {
co_return;
}
cmd_t cmd = cmd_t::cmd_heartbeat;
auto [ec, _] = co_await asio::async_write(*socket, asio::buffer(&cmd, sizeof(cmd)));
if (ec) {
spdlog::error("send cmd_heartbeat failed, {}", ec.message());
co_return;
}

spdlog::trace("send cmd_heartbeat successfully, {}", ec.message());
timer.expires_after(std::chrono::seconds(3));
co_await timer.async_wait(asio::use_awaitable);
}
}

asio::awaitable<void> network_manager::client_udp_loop(audio_manager::AudioFormat audio_format, const std::string host, uint16_t port, uint32_t id)
{
spdlog::info("udp connect: {}:{}, id:{}", host, port, id);

asio::steady_timer timer(*_ioc);
ip::udp::resolver resolver(*_ioc);
ip::udp::endpoint endpoint = *resolver.resolve(asio::ip::udp::v4(), host, std::to_string(port)).begin();
ip::udp::socket socket(*_ioc, asio::ip::udp::v4());

asio::error_code ec{};
uint32_t n;
co_await socket.async_connect(endpoint, asio::redirect_error(asio::use_awaitable, ec));

n = co_await socket.async_send(asio::buffer(&id, sizeof(id)), asio::redirect_error(asio::use_awaitable, ec));
if (ec) {
spdlog::error("udp send id failed, {}", ec.message());
}
spdlog::info("send size: {}, content: {}", n, std::format("{:08x}", id));

std::array<char, 4096> recv_buffer {};
_audio_manager->audio_init(audio_format);
_audio_manager->audio_start();
while (true) {
if (!is_running()) {
co_return;
}
n = co_await socket.async_receive(asio::buffer(recv_buffer), asio::redirect_error(asio::use_awaitable, ec));
if (ec) {
continue;
}
_audio_manager->audio_play(std::vector<char>(recv_buffer.begin(), recv_buffer.begin() + n));
}
}

asio::awaitable<void> network_manager::client_connect(std::shared_ptr<network_manager> self, const std::string host, uint16_t port)
{
audio_manager::AudioFormat audio_format;
uint32_t udp_id = 0;
auto socket = std::make_shared<tcp_socket>(*self->_ioc);
ip::tcp::resolver resolver(*self->_ioc);
ip::tcp::resolver::results_type endpoints = resolver.resolve(host, std::to_string(port));

try {
// resolve
{
auto [ec, _] = co_await asio::async_connect(*socket, endpoints);
if (ec) {
spdlog::error("error connecting to server: {}", ec.message());
co_return;
}
}

// get audio format
{
cmd_t cmd = cmd_t::cmd_get_format;
uint32_t size = 0;
auto [ec, _] = co_await asio::async_write(*socket, asio::buffer(&cmd, sizeof(cmd)));
if (ec) {
spdlog::error("send cmd_get_format error, {}", ec.message());
co_return;
}
spdlog::info("send cmd_get_format successfully, cmd: {}", (size_t)cmd);

cmd = cmd_t::cmd_none;
std::array<uint32_t, 2> buffer = { static_cast<uint32_t>(cmd), size };
std::tie(ec, _) = co_await asio::async_read(*socket, asio::buffer(buffer.data(), sizeof(buffer)));
if (ec) {
spdlog::error("read cmd_get_format error, {}", ec.message());
co_return;
}
cmd = static_cast<cmd_t>(buffer[0]);
size = buffer[1];
if (cmd != cmd_t::cmd_get_format || size <= 0) {
spdlog::error("read cmd_get_format error, cmd: {}, size: {}", (size_t)cmd, size);
co_return;
}
spdlog::info("read cmd_get_format successfully, cmd: {}, size: {}", (size_t)cmd, size);

std::vector<char> format(size);
std::tie(ec, _) = co_await asio::async_read(*socket, asio::buffer(format, size));
if (ec) {
spdlog::error("error read audio format, {}", ec.message());
co_return;
}
std::string str(format.begin(), format.end());
if (!audio_format.ParseFromString(str)) {
spdlog::error("error parse audio format");
co_return;
}
spdlog::info("get audio format successfully, sample_rate: {}, channels: {}, encoding: {}",
(uint32_t)audio_format.sample_rate(), (uint32_t)audio_format.channels(), (uint32_t)audio_format.encoding());
}

// start play
{
cmd_t cmd = cmd_t::cmd_start_play;
auto [ec, _] = co_await asio::async_write(*socket, asio::buffer(&cmd, sizeof(cmd)));
if (ec) {
spdlog::error("error send cmd_start_play, {}", ec.message());
co_return;
}

cmd = cmd_t::cmd_none;
std::array<uint32_t, 2> buffer = { static_cast<uint32_t>(cmd), udp_id};
std::tie(ec, _) = co_await asio::async_read(*socket, asio::buffer(buffer.data(), sizeof(buffer)));
if (ec) {
spdlog::error("error read cmd_start_play. {}", ec.message());
co_return;
}
cmd = static_cast<cmd_t>(buffer[0]);
if (cmd != cmd_t::cmd_start_play) {
spdlog::error("read cmd_start_play error, cmd: {}, udp_id: {}", (size_t)cmd, udp_id);
co_return;
}

udp_id = buffer[1];
spdlog::info("get udp_id successfully, udp_id: {}", std::format("{:08x}", udp_id));
}

asio::co_spawn(*self->_ioc, self->client_heartbeat_loop(socket), asio::detached);
asio::co_spawn(*self->_ioc, self->client_udp_loop(audio_format, host, port, udp_id), asio::detached);
} catch (std::exception& e) {
spdlog::error("error connecting to server: {}", e.what());
}
}

void network_manager::wait_client()
{
_net_thread.join();
}

void network_manager::stop_client()
{
if (_ioc) {
_ioc->stop();
}
_net_thread.join();
_ioc = nullptr;
spdlog::info("client stopped");
}
8 changes: 7 additions & 1 deletion server-core/src/network_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,20 @@ class network_manager : public std::enable_shared_from_this<network_manager>
void start_server(const std::string& host, uint16_t port, const audio_manager::capture_config& capture_config);
void stop_server();
void wait_server();
void start_client(const std::string& host, uint16_t port);
void stop_client();
void wait_client();
bool is_running() const;

private:
asio::awaitable<void> accept_tcp_loop(tcp_acceptor acceptor);
asio::awaitable<void> read_loop(std::shared_ptr<tcp_socket> peer);
asio::awaitable<void> heartbeat_loop(std::shared_ptr<tcp_socket> peer);
asio::awaitable<void> accept_udp_loop();

asio::awaitable<void> client_connect(std::shared_ptr<network_manager> self, const std::string host, uint16_t port);
asio::awaitable<void> client_heartbeat_loop(std::shared_ptr<tcp_socket> socket);
asio::awaitable<void> client_udp_loop(audio_manager::AudioFormat audio_format, const std::string host, uint16_t port, uint32_t id);

playing_peer_list_t::iterator close_session(std::shared_ptr<tcp_socket>& peer);
int add_playing_peer(std::shared_ptr<tcp_socket>& peer);
playing_peer_list_t::iterator remove_playing_peer(std::shared_ptr<tcp_socket>& peer);
Expand Down
Loading