From e6c1c0afc69ee13465bb68a6bdeb35b6876146d4 Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Sat, 6 Jan 2024 19:54:21 -0500 Subject: Initial synced playback Signed-off-by: Andrew Opalach --- src/fruits/cmv/cmv2.c | 363 -------------------------------------------- src/fruits/cmv/meson.build | 10 -- src/fruits/cmv/tui.c | 165 -------------------- src/fruits/cmv/tui.h | 28 ---- src/fruits/sink/meson.build | 10 ++ src/fruits/sink/sink.c | 201 ++++++++++++++++++++++++ 6 files changed, 211 insertions(+), 566 deletions(-) delete mode 100644 src/fruits/cmv/cmv2.c delete mode 100644 src/fruits/cmv/meson.build delete mode 100644 src/fruits/cmv/tui.c delete mode 100644 src/fruits/cmv/tui.h create mode 100644 src/fruits/sink/meson.build create mode 100644 src/fruits/sink/sink.c (limited to 'src/fruits') diff --git a/src/fruits/cmv/cmv2.c b/src/fruits/cmv/cmv2.c deleted file mode 100644 index 89e3427..0000000 --- a/src/fruits/cmv/cmv2.c +++ /dev/null @@ -1,363 +0,0 @@ -#define CMV_USE_TUI 0 -#define CMV_USE_SOCKET 0 - -#include -#include -#if CMV_USE_SOCKET -#include -#endif - -#include "../../codec/libav/common.h" - -#include "../../mixer/audio_miniaudio.h" - -#include "../../screen/screen.h" -#include "../../render/renderer_libplacebo.h" -//#include "../../render/renderer_tiger.h" - -#include "../../libsink/sink2.h" -#include "../../tree/common.h" - -#include "../../shoki/src/search.h" - -#include "../cap/cap.h" - -#if CMV_USE_TUI -#include "tui.h" -#endif - -struct cmv { - s32 quit; - struct camu_screen scr; - struct camu_renderer *renderer; - struct camu_mixer mixer; - struct aki_event_loop loop; - struct camu_sink sink; - struct cap_runner cap; -#if CMV_USE_SOCKET - struct aki_socket socket; - struct aki_line_processor pro; -#endif -#if CMV_USE_TUI - struct cmv_tui tui; - struct aki_timer timer; -#endif -}; - -static str *default_list = al_str_c("default"); - -static u8 sink_callback(void *userdata, u8 op, u8 type, void *opaque) -{ - struct cmv *c = (struct cmv *)userdata; - switch (op) { - case CAMU_SINK_ADD_BUFFER: - switch (type) { - case CAMU_SINK_AUDIO: { - struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque; - camu_mixer_add_buffer(&c->mixer, buf); - al_log_debug("cmv", "Audio buffer added."); - break; - } - case CAMU_SINK_VIDEO: { - struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque; - camu_screen_add_buffer(&c->scr, buf); - al_log_debug("cmv", "Video buffer added."); - break; - } - } - break; - case CAMU_SINK_REMOVE_BUFFER: - switch (type) { - case CAMU_SINK_AUDIO: { - struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque; - camu_mixer_remove_buffer(&c->mixer, buf); - al_log_debug("cmv", "Audio buffer removed."); - break; - } - case CAMU_SINK_VIDEO: { - struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque; - camu_screen_remove_buffer(&c->scr, buf); - al_log_debug("cmv", "Video buffer removed."); - break; - } - } - break; - case CAMU_SINK_SWAP_BUFFER: - switch (type) { - case CAMU_SINK_AUDIO: { - struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque; - camu_mixer_remove_buffer(&c->mixer, buf); - if (cap_list_set_completed(&c->cap, default_list)) { - al_log_debug("cmv", "Audio buffers swapped (gapless)."); - return CAMU_SINK_BUFFERS_SWAPPED; - } else { - al_log_debug("cmv", "Audio buffer removed."); - } - break; - } - } - break; - case CAMU_SINK_SET_BUFFERED: { - cap_list_pump(&c->cap, default_list, true); - break; - } - case CAMU_SINK_START: - switch (type) { - case CAMU_SINK_AUDIO: { - camu_mixer_resume(&c->mixer); - al_log_debug("cmv", "Audio started."); - break; - } - case CAMU_SINK_VIDEO: { - camu_screen_set_state(&c->scr, CAMU_SCREEN_PLAYING); - camu_screen_wake(&c->scr); - al_log_debug("cmv", "Video started."); - break; - } - } - break; - case CAMU_SINK_STOP: - switch (type) { - case CAMU_SINK_AUDIO: { - camu_mixer_pause(&c->mixer); - al_log_debug("cmv", "Audio stopped."); - break; - } - case CAMU_SINK_VIDEO: { - camu_screen_set_state(&c->scr, CAMU_SCREEN_PAUSED); - al_log_debug("cmv", "Video stopped."); - break; - } - } - break; - case CAMU_SINK_EXIT: -#if CMV_USE_SOCKET - aki_line_processor_stop(&c->pro); - aki_socket_close(&c->socket); -#endif - camu_sink_close(&c->sink); - aki_event_loop_break(&c->loop); - break; - } - return CAMU_SINK_OK; -} - -static void screen_callback(void *userdata, u8 op, f64 float0) -{ - struct cmv *c = (struct cmv *)userdata; - switch (op) { - case CAMU_SCREEN_NEXT: - cap_list_skip(&c->cap, default_list, 1); - break; - case CAMU_SCREEN_PREVIOUS: - cap_list_skip(&c->cap, default_list, -1); - break; - case CAMU_SCREEN_TOGGLE_PAUSE: - camu_sink_toggle_pause(&c->sink); - break; - case CAMU_SCREEN_SEEK: - camu_sink_seek(&c->sink, float0); - break; - case CAMU_SCREEN_CLOSE: - c->quit = 1; - break; - } -} - -/* -static bool cap_callback(void *userdata, u8 op, str *name, str *unique_id, void *opaque) -{ - struct cmv *c = (struct cmv *)userdata; - (void)name; - switch (op) { - case CAP_BUFFER: - return camu_sink_local_buffer(&c->sink, unique_id, (struct cch_entry *)opaque); - case CAP_SET: - camu_sink_local_set(&c->sink, unique_id); - al_log_info("cmv", "Now playing: %.*s.", AL_STR_PRINTF(unique_id)); - break; - case CAP_SWAP: - camu_sink_local_swap(&c->sink, unique_id); - break; - case CAP_UNLOAD: - camu_sink_local_unload(&c->sink, unique_id); - break; - } - return true; -} -*/ - -#if CMV_USE_SOCKET -static u8 line_callback(void *userdata, str *line) -{ - struct cmv *c = (struct cmv *)userdata; - if (al_str_eq(line, al_str_c(";NEXT"))) { - cap_list_skip(&c->cap, default_list, 1); - } else if (al_str_eq(line, al_str_c(";PREV"))) { - cap_list_skip(&c->cap, default_list, -1); - } else if (al_str_eq(line, al_str_c(";SHUFFLE"))) { - cap_list_shuffle(&c->cap, default_list); - } else { - cap_list_add(&c->cap, default_list, line); - } - return AKI_LINE_PROCESSOR_CONTINUE; -} -#endif - -#if CMV_USE_TUI -static void timer_callback(void *userdata, struct aki_timer *timer) -{ - struct cmv *c = (struct cmv *)userdata; - struct camu_sink_entry *entry = camu_sink_get_current(&c->sink); - if (entry) { - tui_draw(&c->tui, &((struct camu_sink_local *)entry)->runner, &entry->clock); - } else { - tui_draw(&c->tui, NULL, NULL); - } - camu_sink_return_current(&c->sink); - aki_timer_again(timer); -} - -static s32 log_callback(void *userdata, char *s) -{ - struct cmv *c = (struct cmv *)userdata; - tui_push_log_msg(&c->tui, s); - return 0; -} - -#ifdef HAVE_FFMPEG -static void lav_log_callback(void *userdata, s32 level, const char *fmt, va_list args) -{ - (void)userdata; - if (level < AV_LOG_VERBOSE) al_logv("warn", "lav_internal", (char *)fmt, args); -} -#endif -#endif - -static aki_thread_result AKI_THREADCALL event_loop_thread(void *userdata) -{ - struct cmv *c = (struct cmv *)userdata; - aki_event_loop_run(&c->loop); - aki_event_loop_destroy(&c->loop); - return 0; -} - -static struct cmv c = { 0 }; - -static void sigint_handler(s32 signum) -{ - (void)signum; - c.quit = 1; -} - -s32 main(s32 argc, char *argv[]) -{ - if (!aki_common_init()) return EXIT_FAILURE; - - signal(SIGINT, sigint_handler); - - aki_event_loop_init(&c.loop); - -#if CMV_USE_TUI - if (!tui_init(&c.tui, &c.loop)) { - goto err; - }; - al_set_print(log_callback, &c); -#ifdef HAVE_FFMPEG - camu_lav_set_log_callback(lav_log_callback); -#endif -#endif - - c.scr.callback = screen_callback; - c.scr.userdata = &c; - if (!camu_screen_init(&c.scr) || !camu_screen_create_window(&c.scr, "cmv")) { - goto err; - } - c.renderer = camu_renderer_lp_create(); - //c.renderer = camu_renderer_tiger_create(); - if (!camu_screen_create_renderer(&c.scr, c.renderer)) { - goto err; - } - c.renderer->render(c.renderer, &c.scr); - -#if CAP_USE_PYTHON - bool py_init = argc == 1 ? sho_python_init() : false; -#endif - - camu_mixer_init(&c.mixer, (struct camu_audio *)&audio_plugin_miniaudio); - c.mixer.audio->configure_stream(c.mixer.audio, NULL); - - camu_sink_init(&c.sink, &c.loop, &c.mixer, c.renderer); - c.sink.callback = sink_callback; - c.sink.userdata = &c; - camu_sink_connect(&c.sink, al_str_c("127.0.0.1"), TREE_PORT); - - /* - cap_init(&c.cap, cap_callback, &c); - cap_make_list(&c.cap, default_list); - for (s32 i = 1; i < argc; i++) { - cap_list_add(&c.cap, default_list, al_str_c(argv[i])); - } - */ - -#if CMV_USE_SOCKET - c.socket.type = AKI_SOCKET_UNIX; - aki_socket_init(&c.socket); - aki_socket_set_blocking(&c.socket, false); - c.pro.callback = line_callback; - c.pro.userdata = &c; - aki_line_processor_init(&c.pro, al_str_c("\n")); - aki_line_processor_open_socket(&c.pro, &c.socket); - if (aki_socket_listen(&c.socket, al_str_c("/tmp/cmv_sock"), 0)) { - aki_line_processor_run(&c.pro, &c.loop); - } -#endif - -#if CMV_USE_TUI - aki_timer_init(&c.timer, &c.loop, timer_callback, &c); - aki_timer_set_repeat(&c.timer, 0.05); - aki_timer_again(&c.timer); -#endif - - struct aki_thread thread0; - aki_thread_create(&thread0, event_loop_thread, &c); - - c.quit = 0; - - while (!c.quit) { - if (camu_screen_tick(&c.scr)) { - c.renderer->render(c.renderer, &c.scr); - } - } - - camu_screen_set_state(&c.scr, CAMU_SCREEN_CLOSED); - - camu_sink_stop(&c.sink); - - aki_thread_join(&thread0); - - camu_mixer_close(&c.mixer); - c.renderer->free(&c.renderer); - camu_screen_close(&c.scr); - - //cap_close(&c.cap); - -#if CAP_USE_PYTHON - if (py_init) sho_python_close(); -#endif - -#if CMV_USE_TUI - tui_close(&c.tui); -#endif - - aki_common_close(); - - return EXIT_SUCCESS; -err: -#if CMV_USE_TUI - tui_close(&c.tui); -#endif - aki_event_loop_destroy(&c.loop); - aki_common_close(); - return EXIT_FAILURE; -} diff --git a/src/fruits/cmv/meson.build b/src/fruits/cmv/meson.build deleted file mode 100644 index 1c9ed2d..0000000 --- a/src/fruits/cmv/meson.build +++ /dev/null @@ -1,10 +0,0 @@ -cmv_src = ['cmv2.c'] -cmv_deps = [common_deps, buffer, cache, av, screen, render, mixer, bimu, libsink, cap] - -use_tui = true -if use_tui - cmv_src += ['tui.c'] - cmv_deps += [dependency('notcurses')] -endif - -executable('cmv', cmv_src, dependencies: cmv_deps) diff --git a/src/fruits/cmv/tui.c b/src/fruits/cmv/tui.c deleted file mode 100644 index 8315a13..0000000 --- a/src/fruits/cmv/tui.c +++ /dev/null @@ -1,165 +0,0 @@ -#include -#include -#include -#include - -#include "tui.h" - -static struct cmv_tui *tui_global; - -static void update_term_size(struct cmv_tui *tui) -{ - struct winsize w; - ioctl(tui->fd, TIOCGWINSZ, &w); - tui->width = w.ws_col; - tui->height = w.ws_row; -} - -static void handle_winch(s32 sig) -{ - (void)sig; - struct cmv_tui *tui = tui_global; - aki_mutex_lock(&tui->mutex); - tui->update_size = true; - aki_mutex_unlock(&tui->mutex); -} - -static void input_poll_callback(void *userdata, s32 revents) -{ - struct cmv_tui *tui = (struct cmv_tui *)userdata; - (void)revents; - struct ncinput input; - u32 ret; - while (1) { - ret = ncdirect_get_nblock(tui->dir, &input); - if (ret == (u32)-1 || ret == 0) break; - if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) { - } - } -} - -bool tui_init(struct cmv_tui *tui, struct aki_event_loop *loop) -{ - tui->update_size = true; - tui->width = 0; - tui->height = 0; - tui->prev_lines = 0; - - tui->fd = fileno(stdout); - tui->out = fdopen(tui->fd, "w"); - if (!tui->out) return false; - tui->buffer = al_malloc(256); - - //if (!(tui->dir = ncdirect_init(NULL, tui->out, 0))) { - // fclose(tui->out); - // return false; - //} - - //aki_poll_init(&tui->poll, ncdirect_inputready_fd(tui->dir), AKI_POLL_READ, input_poll_callback, tui); - //aki_poll_start(&tui->poll, loop); - - tui_global = tui; - aki_mutex_init(&tui->mutex); - signal(SIGWINCH, handle_winch); - - // Disable buffer. - setbuf(tui->out, NULL); - - // Disable echo. - tcgetattr(tui->fd, &tui->term); - tui->term.c_lflag &= ~ECHO; - tcsetattr(tui->fd, 0, &tui->term); - - al_queue_init(&tui->log_buffer, 256); - - return true; -} - -void tui_push_log_msg(struct cmv_tui *tui, char *msg) -{ - al_queue_push(&tui->log_buffer, msg); -} - -static void draw_now_playing(struct cmv_tui *tui, struct bmu_local *runner, struct camu_clock *clock) -{ - char *buffer = tui->buffer; - f64 pts = camu_clock_get_pts(clock); - f64 duration = bmu_local_get_duration(runner); - s32 text = 0; - s64 minute = (s64)(pts / 60L); - s64 hour = minute / 60L; - minute -= hour * 60L; - text += al_sprintf(buffer + text, "["); - if (hour > 0) text += al_sprintf(buffer + text, "%.2ld:", hour); - text += al_sprintf(buffer + text, "%.2ld:%.2ld/", minute, (s64)pts % 60L); - minute = (s64)(duration / 60L); - hour = minute / 60L; - minute -= hour * 60L; - if (hour > 0) text += al_sprintf(buffer + text, "%.2ld:", hour); - text += al_sprintf(buffer + text, "%.2ld:%.2ld", minute, (s64)duration % 60L); - text += al_sprintf(buffer + text, "]"); - s32 parts = 0; - if (pts > 0.0 && duration != 0.0) { - if (pts > duration) pts = duration; - f64 percent = pts / duration; - parts = ((tui->width - text) * percent); - } else if (duration == 0.0) { - parts = (tui->width - text); - } - al_memset(buffer + text, '-', parts); - buffer[text + parts] = '\0'; - fprintf(tui->out, "%s", buffer); -} - -void tui_draw(struct cmv_tui *tui, struct bmu_local *runner, struct camu_clock *clock) -{ - aki_mutex_lock(&tui->mutex); - if (tui->update_size) { - update_term_size(tui); - tui->update_size = false; - } - aki_mutex_unlock(&tui->mutex); - - // Erase line. - fprintf(tui->out, "\r\033[K"); - for (u32 i = 0; i < tui->prev_lines; i++) { - // Up one, erase line. - fprintf(tui->out, "\033[A\r\033[K"); - } - - char *msg; - while (al_queue_pop(&tui->log_buffer, (void **)&msg)) { - fprintf(tui->out, "%s\n", msg); - al_free(msg); - } - - // Simply printing each log message with a newline will leave - // a one character high space in the bottom of the terminal window. - // We use that to display a very basic status line (like mpv). - // Trying to use more than that one line space without a more complete - // terminal interface gets scuffed very fast. - - tui->prev_lines = 0; - - if (runner) { - draw_now_playing(tui, runner, clock); - } -} - -void tui_close(struct cmv_tui *tui) -{ - if (!tui->out) return; - al_free(tui->buffer); - char *msg; - while (al_queue_pop(&tui->log_buffer, (void **)&msg)) { - al_free(msg); - } - al_queue_free(&tui->log_buffer); - aki_mutex_destroy(&tui->mutex); - fprintf(tui->out, "\n"); - fflush(tui->out); - tui->term.c_lflag |= ECHO; - tcsetattr(tui->fd, 0, &tui->term); - //ncdirect_stop(tui->dir); - fclose(tui->out); -} diff --git a/src/fruits/cmv/tui.h b/src/fruits/cmv/tui.h deleted file mode 100644 index 6898539..0000000 --- a/src/fruits/cmv/tui.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "../../bimu/local.h" -#include "../../buffer/video.h" - -struct cmv_tui { - s32 fd; - FILE *out; - char *buffer; - struct ncdirect *dir; - struct aki_poll poll; - struct aki_mutex mutex; - struct termios term; - bool update_size; - u32 width; - u32 height; - u32 prev_lines; - queue log_buffer; -}; - -bool tui_init(struct cmv_tui *tui, struct aki_event_loop *loop); -void tui_push_log_msg(struct cmv_tui *tui, char *msg); -void tui_draw(struct cmv_tui *tui, struct bmu_local *runner, struct camu_clock *clock); -void tui_close(struct cmv_tui *tui); diff --git a/src/fruits/sink/meson.build b/src/fruits/sink/meson.build new file mode 100644 index 0000000..c409857 --- /dev/null +++ b/src/fruits/sink/meson.build @@ -0,0 +1,10 @@ +sink_src = ['sink.c'] +sink_deps = [common_deps, buffer, cache, av, screen, render, mixer, libsink] + +use_tui = false +if use_tui + sink_src += [] + sink_deps += [dependency('notcurses')] +endif + +executable('sink', sink_src, dependencies: sink_deps) diff --git a/src/fruits/sink/sink.c b/src/fruits/sink/sink.c new file mode 100644 index 0000000..d0455a4 --- /dev/null +++ b/src/fruits/sink/sink.c @@ -0,0 +1,201 @@ +#include +#include + +#include "../../codec/libav/common.h" + +#include "../../mixer/audio_miniaudio.h" + +#include "../../screen/screen.h" +#include "../../render/renderer_libplacebo.h" +//#include "../../render/renderer_tiger.h" + +#include "../../libsink/sink.h" +#include "../../tree/common.h" + +#include "../../shoki/src/search.h" + +struct cmv { + s32 quit; + struct camu_screen scr; + struct camu_renderer *renderer; + struct camu_mixer mixer; + struct aki_event_loop loop; + struct camu_sink sink; +}; + +static u8 sink_callback(void *userdata, u8 op, u8 type, void *opaque) +{ + struct cmv *c = (struct cmv *)userdata; + switch (op) { + case CAMU_SINK_ADD_BUFFER: + switch (type) { + case CAMU_SINK_AUDIO: { + struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque; + camu_mixer_add_buffer(&c->mixer, buf); + al_log_debug("cmv", "Audio buffer added."); + break; + } + case CAMU_SINK_VIDEO: { + struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque; + camu_screen_add_buffer(&c->scr, buf); + al_log_debug("cmv", "Video buffer added."); + break; + } + } + break; + case CAMU_SINK_REMOVE_BUFFER: + switch (type) { + case CAMU_SINK_AUDIO: { + struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque; + camu_mixer_remove_buffer(&c->mixer, buf); + al_log_debug("cmv", "Audio buffer removed."); + break; + } + case CAMU_SINK_VIDEO: { + struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque; + camu_screen_remove_buffer(&c->scr, buf); + al_log_debug("cmv", "Video buffer removed."); + break; + } + } + break; + case CAMU_SINK_SWAP_BUFFER: + switch (type) { + case CAMU_SINK_AUDIO: { + break; + } + } + break; + case CAMU_SINK_SET_BUFFERED: { + break; + } + case CAMU_SINK_START: + switch (type) { + case CAMU_SINK_AUDIO: { + camu_mixer_resume(&c->mixer); + al_log_debug("cmv", "Audio started."); + break; + } + case CAMU_SINK_VIDEO: { + camu_screen_set_state(&c->scr, CAMU_SCREEN_PLAYING); + camu_screen_wake(&c->scr); + al_log_debug("cmv", "Video started."); + break; + } + } + break; + case CAMU_SINK_STOP: + switch (type) { + case CAMU_SINK_AUDIO: { + camu_mixer_pause(&c->mixer); + al_log_debug("cmv", "Audio stopped."); + break; + } + case CAMU_SINK_VIDEO: { + camu_screen_set_state(&c->scr, CAMU_SCREEN_PAUSED); + al_log_debug("cmv", "Video stopped."); + break; + } + } + break; + case CAMU_SINK_EXIT: + camu_sink_close(&c->sink); + aki_event_loop_break(&c->loop); + break; + } + return CAMU_SINK_OK; +} + +static void screen_callback(void *userdata, u8 op, f64 float0) +{ + struct cmv *c = (struct cmv *)userdata; + switch (op) { + case CAMU_SCREEN_NEXT: + break; + case CAMU_SCREEN_PREVIOUS: + break; + case CAMU_SCREEN_TOGGLE_PAUSE: + camu_sink_toggle_pause(&c->sink); + break; + case CAMU_SCREEN_SEEK: + camu_sink_seek(&c->sink, float0); + break; + case CAMU_SCREEN_CLOSE: + c->quit = 1; + break; + } +} + +static aki_thread_result AKI_THREADCALL event_loop_thread(void *userdata) +{ + struct cmv *c = (struct cmv *)userdata; + aki_event_loop_run(&c->loop); + aki_event_loop_destroy(&c->loop); + return 0; +} + +static struct cmv c = { 0 }; + +static void sigint_handler(s32 signum) +{ + (void)signum; + c.quit = 1; +} + +s32 main(s32 argc, char *argv[]) +{ + if (!aki_common_init()) return EXIT_FAILURE; + + signal(SIGINT, sigint_handler); + + aki_event_loop_init(&c.loop); + + c.scr.callback = screen_callback; + c.scr.userdata = &c; + if (!camu_screen_init(&c.scr) || !camu_screen_create_window(&c.scr, "cmv")) { + goto err; + } + c.renderer = camu_renderer_lp_create(); + //c.renderer = camu_renderer_tiger_create(); + if (!camu_screen_create_renderer(&c.scr, c.renderer)) { + goto err; + } + c.renderer->render(c.renderer, &c.scr); + + camu_mixer_init(&c.mixer, (struct camu_audio *)&audio_plugin_miniaudio); + c.mixer.audio->configure_stream(c.mixer.audio, NULL); + + camu_sink_init(&c.sink, &c.loop, &c.mixer, c.renderer); + c.sink.callback = sink_callback; + c.sink.userdata = &c; + camu_sink_connect(&c.sink, al_str_c("127.0.0.1"), TREE_PORT); + + struct aki_thread thread0; + aki_thread_create(&thread0, event_loop_thread, &c); + + c.quit = 0; + + while (!c.quit) { + if (camu_screen_tick(&c.scr)) { + c.renderer->render(c.renderer, &c.scr); + } + } + + camu_screen_set_state(&c.scr, CAMU_SCREEN_CLOSED); + + camu_sink_stop(&c.sink); + + aki_thread_join(&thread0); + + camu_mixer_close(&c.mixer); + c.renderer->free(&c.renderer); + camu_screen_close(&c.scr); + + aki_common_close(); + + return EXIT_SUCCESS; +err: + aki_event_loop_destroy(&c.loop); + aki_common_close(); + return EXIT_FAILURE; +} -- cgit v1.2.3-101-g0448