Implement the proximity voice chat

main
Cameron Murphy Reikes 2 years ago
parent 9eec314031
commit c374c7113b

@ -1,2 +1,2 @@
set compileopts=/Fe"flight_debug" /Zi /FS /Fd"flight.pdb" /DSERVER_ADDRESS="\"127.0.0.1\"" set compileopts=/Fe"flight_debug" /Zi /FS /Fd"flight.pdb" /DSERVER_ADDRESS="\"127.0.0.1\"" /DDEBUG_RENDERING
call build_msvc.bat call build_msvc.bat

@ -1155,6 +1155,7 @@ sapp_desc
sokol_main(int argc, char* argv[]) sokol_main(int argc, char* argv[])
{ {
bool hosting = false; bool hosting = false;
stm_setup();
ma_mutex_init(&server_info.info_mutex); ma_mutex_init(&server_info.info_mutex);
server_info.world_save = "debug_world.bin"; server_info.world_save = "debug_world.bin";
if (argc > 1) { if (argc > 1) {

@ -53,7 +53,6 @@ static SpallBuffer spall_buffer;
#endif #endif
// started in a thread from host // started in a thread from host
void server(void* info_raw) void server(void* info_raw)
{ {
@ -71,7 +70,6 @@ void server(void* info_raw)
#endif #endif
stm_setup();
struct GameState gs = { 0 }; struct GameState gs = { 0 };
size_t entities_size = (sizeof(Entity) * MAX_ENTITIES); size_t entities_size = (sizeof(Entity) * MAX_ENTITIES);
@ -189,6 +187,8 @@ void server(void* info_raw)
ENetEvent event; ENetEvent event;
uint64_t last_processed_time = stm_now(); uint64_t last_processed_time = stm_now();
uint64_t last_saved_world_time = stm_now(); uint64_t last_saved_world_time = stm_now();
uint64_t last_sent_audio_time = stm_now();
float audio_time_to_send = 0.0f;
float total_time = 0.0f; float total_time = 0.0f;
size_t player_to_latest_id_processed[MAX_PLAYERS] = { 0 }; size_t player_to_latest_id_processed[MAX_PLAYERS] = { 0 };
char* world_save_buffer = malloc(entities_size); char* world_save_buffer = malloc(entities_size);
@ -269,7 +269,10 @@ void server(void* info_raw)
else { else {
int64_t player_slot = (int64_t)event.peer->data; int64_t player_slot = (int64_t)event.peer->data;
size_t length = event.packet->dataLength; size_t length = event.packet->dataLength;
struct ClientToServer received = { .mic_data = player_voip_buffers[player_slot] }; OpusBuffer throwaway_buffer = { 0 };
OpusBuffer* buffer_to_fill = player_voip_buffers[player_slot];
if (get_entity(&gs, gs.players[player_slot].entity) == NULL) buffer_to_fill = &throwaway_buffer;
struct ClientToServer received = { .mic_data = buffer_to_fill };
if (!client_to_server_deserialize(&gs, &received, event.packet->data, event.packet->dataLength)) if (!client_to_server_deserialize(&gs, &received, event.packet->data, event.packet->dataLength))
{ {
Log("Bad packet from client %d\n", (int)player_slot); Log("Bad packet from client %d\n", (int)player_slot);
@ -325,7 +328,9 @@ void server(void* info_raw)
entity_destroy(&gs, player_body); entity_destroy(&gs, player_body);
} }
opus_encoder_destroy(player_encoders[player_index]); opus_encoder_destroy(player_encoders[player_index]);
player_encoders[player_index] = NULL;
opus_decoder_destroy(player_decoders[player_index]); opus_decoder_destroy(player_decoders[player_index]);
player_decoders[player_index] = NULL;
gs.players[player_index].connected = false; gs.players[player_index].connected = false;
clear_buffer(player_voip_buffers[player_index]); clear_buffer(player_voip_buffers[player_index]);
event.peer->data = NULL; event.peer->data = NULL;
@ -402,6 +407,38 @@ void server(void* info_raw)
{ {
PROFILE_SCOPE("send_data") { PROFILE_SCOPE("send_data") {
static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = { 0 }; static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = { 0 };
audio_time_to_send += (float)stm_sec(stm_diff(stm_now(), last_sent_audio_time));
last_sent_audio_time = stm_now();
int num_audio_packets = (int)floor(1.0f / (VOIP_TIME_PER_PACKET / audio_time_to_send));
#define MAX_AUDIO_PACKETS_TO_SEND 6
if (num_audio_packets > MAX_AUDIO_PACKETS_TO_SEND)
{
Log("Wants %d, this is too many packets. Greater than the maximum %d\n", num_audio_packets, MAX_AUDIO_PACKETS_TO_SEND);
num_audio_packets = MAX_AUDIO_PACKETS_TO_SEND;
}
opus_int16 decoded_audio_packets[MAX_PLAYERS][MAX_AUDIO_PACKETS_TO_SEND][VOIP_EXPECTED_FRAME_COUNT] = { 0 };
audio_time_to_send -= num_audio_packets * VOIP_TIME_PER_PACKET;
// decode what everybody said
CONNECTED_PEERS(enet_host, cur)
{
int this_player_index = (int)(int64_t)cur->data;
for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
{
opus_int16* to_dump_to = decoded_audio_packets[this_player_index][packet_i];
OpusPacket* cur_packet = pop_packet(player_voip_buffers[this_player_index]);
if (cur_packet == NULL)
opus_decode(player_decoders[this_player_index], NULL, 0, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
else
opus_decode(player_decoders[this_player_index], cur_packet->data, cur_packet->length, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
}
}
// send gamestate to each player
CONNECTED_PEERS(enet_host, cur) CONNECTED_PEERS(enet_host, cur)
{ {
int this_player_index = (int)(int64_t)cur->data; int this_player_index = (int)(int64_t)cur->data;
@ -411,10 +448,48 @@ void server(void* info_raw)
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again // @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
char* bytes_buffer = malloc(sizeof * bytes_buffer * MAX_SERVER_TO_CLIENT); char* bytes_buffer = malloc(sizeof * bytes_buffer * MAX_SERVER_TO_CLIENT);
char* compressed_buffer = malloc(sizeof * compressed_buffer * MAX_SERVER_TO_CLIENT); char* compressed_buffer = malloc(sizeof * compressed_buffer * MAX_SERVER_TO_CLIENT);
// mix audio to be sent
OpusBuffer* buffer_to_play = calloc(1, sizeof * buffer_to_play); // @Robust no malloc, also in all other places no malloc
{
for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
{
opus_int16 to_send_to_cur[VOIP_EXPECTED_FRAME_COUNT] = { 0 }; // mix what other players said into this buffer
CONNECTED_PEERS(enet_host, other_player)
{
if (other_player != cur)
{
int other_player_index = (int)(int64_t)other_player->data;
Entity* other_player_entity = get_entity(&gs, gs.players[other_player_index].entity);
if (other_player_entity != NULL)
{
float dist = V2dist(entity_pos(this_player_entity), entity_pos(other_player_entity));
float volume = lerp(1.0f, 0.0f, clamp01(dist / VOIP_DISTANCE_WHEN_CANT_HEAR));
if (volume > 0.01f)
{
for (int frame_i = 0; frame_i < VOIP_EXPECTED_FRAME_COUNT; frame_i++)
{
to_send_to_cur[frame_i] += (opus_int16)((float)decoded_audio_packets[other_player_index][packet_i][frame_i]*volume);
}
}
}
}
}
OpusPacket* this_packet = push_packet(buffer_to_play);
opus_int32 ret = opus_encode(player_encoders[this_player_index], to_send_to_cur, VOIP_EXPECTED_FRAME_COUNT, this_packet->data, VOIP_PACKET_MAX_SIZE);
if (ret < 0)
{
Log("Failed to encode audio packet for player %d: opus error code %d\n", this_player_index, ret);
}
else {
this_packet->length = ret;
}
}
}
ServerToClient to_send = (ServerToClient){ ServerToClient to_send = (ServerToClient){
.cur_gs = &gs, .cur_gs = &gs,
.your_player = this_player_index, .your_player = this_player_index,
.playback_buffer = player_voip_buffers[this_player_index], .playback_buffer = buffer_to_play,
}; };
size_t len = 0; size_t len = 0;
@ -444,6 +519,7 @@ void server(void* info_raw)
{ {
Log("Failed to serialize data for client %d\n", this_player_index); Log("Failed to serialize data for client %d\n", this_player_index);
} }
free(buffer_to_play);
free(bytes_buffer); free(bytes_buffer);
free(compressed_buffer); free(compressed_buffer);
} }

@ -5,6 +5,7 @@
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
stm_setup();
server("world.bin"); server("world.bin");
return 0; return 0;
} }

@ -13,7 +13,7 @@ Sleep, 20
WinActivate, flightbuild WinActivate, flightbuild
If WinActive("flightbuild") If WinActive("flightbuild")
{ {
Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && flight.exe{Enter} Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && flight_debug.exe{Enter}
} }
return return
@ -26,6 +26,6 @@ WinKill, Flight
WinActivate, flightbuild WinActivate, flightbuild
If WinActive("flightbuild") If WinActive("flightbuild")
{ {
Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && START /B flight.exe && flight.exe --host{Enter} Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && START /B flight_debug.exe && flight_debug.exe --host{Enter}
} }
return return

@ -38,8 +38,9 @@
#define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2 #define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2
#define VOIP_EXPECTED_FRAME_COUNT 480 #define VOIP_EXPECTED_FRAME_COUNT 480
#define VOIP_SAMPLE_RATE 48000 #define VOIP_SAMPLE_RATE 48000
#define VOIP_TIME_PER_PACKET 1.0f / ((float)(VOIP_SAMPLE_RATE/VOIP_EXCPECTED_FRAME_COUNT)) // in seconds #define VOIP_TIME_PER_PACKET 1.0f / ((float)(VOIP_SAMPLE_RATE/VOIP_EXPECTED_FRAME_COUNT)) // in seconds
#define VOIP_PACKET_MAX_SIZE 4000 #define VOIP_PACKET_MAX_SIZE 4000
#define VOIP_DISTANCE_WHEN_CANT_HEAR (BOX_SIZE*13.0f)
#define TIMESTEP (1.0f / 60.0f) // not required to simulate at this, but this defines what tick the game is on #define TIMESTEP (1.0f / 60.0f) // not required to simulate at this, but this defines what tick the game is on
#define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f) #define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
@ -400,6 +401,23 @@ static int num_queued_packets(OpusBuffer* buff)
return to_return; return to_return;
} }
static OpusPacket* get_packet_at_index(OpusBuffer* buff, int i)
{
OpusPacket* to_return = buff->next;
int index_at = 0;
while (index_at < i)
{
if (to_return->next == NULL)
{
Log("FAILED TO GET TO INDEX %d\n", i);
return to_return;
}
to_return = to_return->next;
index_at++;
}
return to_return;
}
// returns null if the packet was dropped, like if the buffer was too full // returns null if the packet was dropped, like if the buffer was too full
static OpusPacket* pop_packet(OpusBuffer* buff) static OpusPacket* pop_packet(OpusBuffer* buff)
{ {
@ -516,6 +534,11 @@ static float V2distsqr(V2 from, V2 to)
return V2lengthsqr(V2sub(to, from)); return V2lengthsqr(V2sub(to, from));
} }
static float V2dist(V2 from, V2 to)
{
return sqrtf(V2distsqr(from, to));
}
static inline float clamp(float f, float minimum, float maximum) static inline float clamp(float f, float minimum, float maximum)
{ {
if (f < minimum) if (f < minimum)

Loading…
Cancel
Save