397 lines
9.5 KiB
C
397 lines
9.5 KiB
C
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_ttf.h>
|
|
#include <stdio.h>
|
|
|
|
#include <semaphore.h>
|
|
#include <pthread.h>
|
|
|
|
#include "texture.h"
|
|
#include "audio.h"
|
|
#include "net.h"
|
|
#include "log.h"
|
|
|
|
#define SCREEN_WIDTH 640
|
|
#define SCREEN_HEIGHT 480
|
|
#define TEXT_LINES 20
|
|
#define BODY_TEXT_LINES TEXT_LINES-2
|
|
#define FONT_SIZE SCREEN_HEIGHT/TEXT_LINES
|
|
|
|
#define PORT 55555
|
|
|
|
typedef enum {
|
|
SOCKET_LISTEN,
|
|
SELECTING_DEVICE,
|
|
RECORDING,
|
|
RECORDED,
|
|
PLAYBACK,
|
|
} application_state;
|
|
|
|
typedef struct {
|
|
application_state application_state;
|
|
audio_state audio;
|
|
int device_index;
|
|
int update_ui;
|
|
sem_t sem;
|
|
} state;
|
|
|
|
int init();
|
|
void destroy();
|
|
int select_device(SDL_KeyboardEvent* key_event, int max_index);
|
|
void application_state_to_string(application_state state, char* string);
|
|
|
|
void* broadcast_t(void* args);
|
|
void* peer_discovery_t(void* args);
|
|
|
|
SDL_Window* window = NULL;
|
|
SDL_Renderer* renderer = NULL;
|
|
|
|
int num_devices = 0;
|
|
|
|
int handle_input(SDL_Event* event, state* state);
|
|
void handle_task(state* state);
|
|
void render_body_text(text_texture* textures, int len);
|
|
int main(int argc, char* argv[])
|
|
{
|
|
if (!init()) return 1;
|
|
|
|
num_devices = SDL_GetNumAudioDevices(1);
|
|
log_message(LOG_INFO, "Num audio devices: %i", num_devices);
|
|
char char_buffer[64];
|
|
char device_selected_fmt[] = "Device selected: %i";
|
|
|
|
state state;
|
|
state.application_state = SOCKET_LISTEN;
|
|
//state.application_state = SELECTING_DEVICE;
|
|
if (!audio_init(&state.audio)) return 1;
|
|
state.device_index = -1;
|
|
state.update_ui = 0;
|
|
sem_init(&state.sem, 0, 1);
|
|
|
|
text_texture display_text_texture;
|
|
text_texture_init(&display_text_texture, renderer, FONT_SIZE);
|
|
sprintf(char_buffer, device_selected_fmt, state.device_index);
|
|
text_texture_load(&display_text_texture, char_buffer);
|
|
|
|
text_texture state_text_texture;
|
|
text_texture_init(&state_text_texture, renderer, FONT_SIZE);
|
|
application_state_to_string(state.application_state, char_buffer);
|
|
text_texture_load(&state_text_texture, char_buffer);
|
|
|
|
text_texture_frame socket_frame;
|
|
text_texture_frame_init(&socket_frame, BODY_TEXT_LINES, renderer, FONT_SIZE);
|
|
|
|
text_texture_frame select_device_frame;
|
|
text_texture_frame_init(&select_device_frame, num_devices, renderer, FONT_SIZE);
|
|
|
|
for (int i = 0; i < select_device_frame.len; i++) {
|
|
sprintf(char_buffer, "%i: %s", i, SDL_GetAudioDeviceName(i, 1));
|
|
text_texture_load(&select_device_frame.textures[i], char_buffer);
|
|
}
|
|
|
|
pthread_t peer_discovery_thread;
|
|
pthread_create(&peer_discovery_thread, NULL, peer_discovery_t, &state);
|
|
|
|
pthread_t broadcast_thread;
|
|
pthread_create(&broadcast_thread, NULL, broadcast_t, &state);
|
|
|
|
SDL_Event event;
|
|
while (1) {
|
|
sem_wait(&state.sem);
|
|
while (SDL_PollEvent(&event)) {
|
|
SDL_Keycode keysym = event.key.keysym.sym;
|
|
switch (event.type) {
|
|
|
|
case SDL_QUIT:
|
|
goto cleanup;
|
|
break;
|
|
|
|
case SDL_KEYDOWN:
|
|
if (!handle_input(&event, &state)) goto cleanup;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
handle_task(&state);
|
|
|
|
if (state.update_ui) {
|
|
sprintf(char_buffer, device_selected_fmt, state.device_index);
|
|
text_texture_load(&display_text_texture, char_buffer);
|
|
application_state_to_string(state.application_state, char_buffer);
|
|
text_texture_load(&state_text_texture, char_buffer);
|
|
log_message(LOG_INFO, "Update UI State: %s", char_buffer);
|
|
|
|
int i = 0;
|
|
struct sockaddr_in* peer_addr;
|
|
while (net_get_peers(&peer_addr)) {
|
|
if (i >= socket_frame.len) break;;
|
|
char* ip = inet_ntoa(peer_addr->sin_addr);
|
|
int port = ntohs(peer_addr->sin_port);
|
|
sprintf(char_buffer, "%s:%i", ip, port);
|
|
text_texture_load(&socket_frame.textures[i], char_buffer);
|
|
i++;
|
|
}
|
|
for (; i < socket_frame.len; i++) text_texture_load(&socket_frame.textures[i], "");
|
|
|
|
state.update_ui = 0;
|
|
}
|
|
|
|
// Clear the screen
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
|
SDL_RenderClear(renderer);
|
|
|
|
// Render
|
|
text_texture_render(&display_text_texture, 0, 0);
|
|
|
|
if (state.application_state == SOCKET_LISTEN) {
|
|
text_texture_frame_render(&socket_frame, 0, FONT_SIZE);
|
|
}
|
|
|
|
if (state.application_state == SELECTING_DEVICE) {
|
|
text_texture_frame_render(&select_device_frame, 0, FONT_SIZE);
|
|
}
|
|
|
|
text_texture_render(&state_text_texture, 0, SCREEN_HEIGHT - state_text_texture.height);
|
|
|
|
// Update the screen
|
|
SDL_RenderPresent(renderer);
|
|
sem_post(&state.sem);
|
|
}
|
|
|
|
cleanup:
|
|
log_message(LOG_INFO, "Cleanup start");
|
|
audio_destroy(&state.audio);
|
|
log_message(LOG_INFO, "Audio done");
|
|
text_texture_destroy(&display_text_texture);
|
|
text_texture_destroy(&state_text_texture);
|
|
text_texture_frame_destroy(&socket_frame);
|
|
text_texture_frame_destroy(&select_device_frame);
|
|
log_message(LOG_INFO, "Texture done");
|
|
destroy();
|
|
log_message(LOG_INFO, "Done");
|
|
return 0;
|
|
}
|
|
|
|
int handle_input(SDL_Event* event, state* state)
|
|
{
|
|
SDL_Keycode keysym = event->key.keysym.sym;
|
|
switch (state->application_state) {
|
|
|
|
case SOCKET_LISTEN:
|
|
switch (keysym) {
|
|
case SDLK_1:
|
|
log_message(LOG_INFO, "Socket button 1 pressed");
|
|
net_broadcast();
|
|
break;
|
|
case SDLK_2:
|
|
log_message(LOG_INFO, "Socket button 2 pressed");
|
|
net_print_peers();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SELECTING_DEVICE:
|
|
switch (keysym) {
|
|
case SDLK_y:
|
|
case SDLK_RETURN:
|
|
if (state->device_index == -1 || !state->audio.recording_device_id) break;
|
|
audio_buffer_reset(&state->audio);
|
|
state->audio.recording_buffer_position = 0;
|
|
audio_device_pause(&state->audio, 1, 0);
|
|
state->application_state = RECORDING;
|
|
state->update_ui = 1;
|
|
break;
|
|
case SDLK_q:
|
|
case SDLK_ESCAPE:
|
|
return 0;
|
|
break;
|
|
}
|
|
int device_index_new = select_device(&event->key, num_devices);
|
|
if (device_index_new == -1) break;
|
|
if (device_index_new == state->device_index) break;
|
|
state->device_index = device_index_new;
|
|
audio_device_close(&state->audio, 1);
|
|
audio_recording_init(&state->audio, state->device_index);
|
|
state->update_ui = 1;
|
|
break;
|
|
|
|
case RECORDING:
|
|
switch (keysym) {
|
|
case SDLK_q:
|
|
case SDLK_ESCAPE:
|
|
audio_device_pause(&state->audio, 1, 1);
|
|
state->application_state = SELECTING_DEVICE;
|
|
state->update_ui = 1;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case RECORDED:
|
|
switch (keysym) {
|
|
case SDLK_y:
|
|
case SDLK_RETURN:
|
|
if (!state->audio.playback_device_id)
|
|
audio_playback_init(&state->audio);
|
|
state->audio.recording_buffer_position = 0;
|
|
audio_device_pause(&state->audio, 0, 0);
|
|
state->application_state = PLAYBACK;
|
|
state->update_ui = 1;
|
|
break;
|
|
case SDLK_q:
|
|
case SDLK_ESCAPE:
|
|
state->application_state = SELECTING_DEVICE;
|
|
state->update_ui = 1;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case PLAYBACK:
|
|
switch (keysym) {
|
|
case SDLK_q:
|
|
case SDLK_ESCAPE:
|
|
audio_device_pause(&state->audio, 0, 1);
|
|
state->application_state = RECORDED;
|
|
state->update_ui = 1;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void handle_task(state* state)
|
|
{
|
|
switch (state->application_state) {
|
|
|
|
case SOCKET_LISTEN:
|
|
break;
|
|
|
|
case SELECTING_DEVICE:
|
|
break;
|
|
|
|
case RECORDING:
|
|
if (state->audio.recording_buffer_position >= state->audio.recording_buffer_size) {
|
|
state->application_state = RECORDED;
|
|
state->update_ui = 1;
|
|
}
|
|
break;
|
|
|
|
case RECORDED:
|
|
break;
|
|
|
|
case PLAYBACK:
|
|
if (state->audio.recording_buffer_position >= state->audio.recording_buffer_size) {
|
|
state->application_state = RECORDED;
|
|
state->update_ui = 1;
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
int init()
|
|
{
|
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
|
|
log_message(LOG_ERROR, "SDL could not initialize! SDL Error: %s", SDL_GetError());
|
|
return 0;
|
|
}
|
|
|
|
if (TTF_Init() == -1) {
|
|
log_message(LOG_ERROR, "TTF could not initialize! TTF Error: %s", TTF_GetError());
|
|
return 0;
|
|
}
|
|
|
|
window = SDL_CreateWindow("SDL record", SDL_WINDOWPOS_UNDEFINED,
|
|
SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
|
|
SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
|
|
|
|
if (!window) {
|
|
log_message(LOG_ERROR, "Window could not be created! SDL Error: %s", SDL_GetError());
|
|
return 0;
|
|
}
|
|
|
|
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
|
if (!renderer) {
|
|
log_message(LOG_ERROR, "Renderer could not be created! SDL_Error: %s", SDL_GetError());
|
|
return 0;
|
|
}
|
|
|
|
net_init(PORT);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void destroy()
|
|
{
|
|
SDL_DestroyRenderer(renderer);
|
|
log_message(LOG_INFO, "Renderer done");
|
|
SDL_DestroyWindow(window);
|
|
log_message(LOG_INFO, "Window done");
|
|
TTF_Quit();
|
|
log_message(LOG_INFO, "TTF done");
|
|
SDL_Quit();
|
|
}
|
|
|
|
int select_device(SDL_KeyboardEvent* key_event, int max_index)
|
|
{
|
|
SDL_Keycode keycode = key_event->keysym.sym;
|
|
/*SDL_Keycode mod = key_event->keysym.mod;*/
|
|
|
|
int keycode_is_num = (keycode >= SDLK_0 && keycode <= SDLK_9);
|
|
/*int no_mod = (mod == 0);*/
|
|
/*if (!keycode_is_num || !no_mod) {*/
|
|
if (!keycode_is_num) {
|
|
log_message(LOG_INFO, "Not number: %c <%u>", keycode, keycode);
|
|
return -1;
|
|
} else {
|
|
log_message(LOG_INFO, "Is number: %c <%u>", keycode, keycode);
|
|
}
|
|
|
|
int index = keycode - SDLK_0;
|
|
if (index >= max_index) return -1;
|
|
|
|
return index;
|
|
}
|
|
|
|
void application_state_to_string(application_state state, char* string)
|
|
{
|
|
switch (state) {
|
|
case SOCKET_LISTEN:
|
|
sprintf(string, "Socket");
|
|
break;
|
|
case SELECTING_DEVICE:
|
|
sprintf(string, "Selecting Device [0-9] -> [y/RET]");
|
|
break;
|
|
case RECORDING:
|
|
sprintf(string, "Recording [ESC]: CANCEL");
|
|
break;
|
|
case RECORDED:
|
|
sprintf(string, "Recorded [y/RET]: PLAY | [ESC]: SELECT");
|
|
break;
|
|
case PLAYBACK:
|
|
sprintf(string, "Playing [ESC]: CANCEL");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void* broadcast_t(void* args)
|
|
{
|
|
while (1) {
|
|
net_broadcast();
|
|
sleep(1);
|
|
}
|
|
}
|
|
|
|
void* peer_discovery_t(void* args)
|
|
{
|
|
state* state = args;
|
|
while (1) {
|
|
int added_client = net_listen_peers();
|
|
int pruned_client = net_prune_peers();
|
|
sem_wait(&state->sem);
|
|
state->update_ui = added_client || pruned_client;
|
|
sem_post(&state->sem);
|
|
}
|
|
}
|