diff --git a/test/test-input/CMakeLists.txt b/test/test-input/CMakeLists.txt index 596e63730..abc2fb6a4 100644 --- a/test/test-input/CMakeLists.txt +++ b/test/test-input/CMakeLists.txt @@ -12,6 +12,9 @@ set(test-input_SOURCES test-filter.c test-input.c test-sinewave.c + sync-async-source.c + sync-pair-vid.c + sync-pair-aud.c test-random.c) add_library(test-input MODULE diff --git a/test/test-input/sync-async-source.c b/test/test-input/sync-async-source.c new file mode 100644 index 000000000..8aa6dd10b --- /dev/null +++ b/test/test-input/sync-async-source.c @@ -0,0 +1,143 @@ +#include +#include +#include +#include + +struct async_sync_test { + obs_source_t *source; + os_event_t *stop_signal; + pthread_t thread; + bool initialized; +}; + +/* middle C */ +static const double rate = 261.63/48000.0; + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define M_PI_X2 M_PI*2 + +static const char *ast_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return "Sync Test (Async Video/Audio Source)"; +} + +static void ast_destroy(void *data) +{ + struct async_sync_test *ast = data; + + if (ast->initialized) { + os_event_signal(ast->stop_signal); + pthread_join(ast->thread, NULL); + } + + os_event_destroy(ast->stop_signal); + bfree(ast); +} + +static inline void fill_texture(uint32_t *pixels, uint32_t color) +{ + size_t x, y; + + for (y = 0; y < 20; y++) { + for (x = 0; x < 20; x++) { + pixels[y*20 + x] = color; + } + } +} + +static void *video_thread(void *data) +{ + struct async_sync_test *ast = data; + + uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); + + uint32_t *pixels = bmalloc(20 * 20 * sizeof(uint32_t)); + float *samples = bmalloc(sample_rate * sizeof(float)); + uint64_t cur_time = os_gettime_ns(); + bool whitelist = false; + double cos_val = 0.0; + uint64_t start_time = cur_time; + + struct obs_source_frame frame = { + .data = {[0] = (uint8_t*)pixels}, + .linesize = {[0] = 20*4}, + .width = 20, + .height = 20, + .format = VIDEO_FORMAT_BGRX + }; + struct obs_source_audio audio = { + .speakers = SPEAKERS_MONO, + .data = {[0] = (uint8_t*)samples}, + .samples_per_sec = sample_rate, + .frames = sample_rate, + .format = AUDIO_FORMAT_FLOAT + }; + + while (os_event_try(ast->stop_signal) == EAGAIN) { + fill_texture(pixels, whitelist ? 0xFFFFFFFF : 0xFF000000); + + frame.timestamp = cur_time - start_time; + audio.timestamp = cur_time - start_time; + + if (whitelist) { + for (size_t i = 0; i < sample_rate; i++) { + cos_val += rate * M_PI_X2; + if (cos_val > M_PI_X2) + cos_val -= M_PI_X2; + + samples[i] = (float)(cos(cos_val) * 0.5); + } + } else { + for (size_t i = 0; i < sample_rate; i++) + samples[i] = 0.0f; + } + + obs_source_output_video(ast->source, &frame); + obs_source_output_audio(ast->source, &audio); + + os_sleepto_ns(cur_time += 1000000000); + + whitelist = !whitelist; + } + + bfree(pixels); + bfree(samples); + + return NULL; +} + +static void *ast_create(obs_data_t *settings, obs_source_t *source) +{ + struct async_sync_test *ast = bzalloc(sizeof(struct async_sync_test)); + ast->source = source; + + if (os_event_init(&ast->stop_signal, OS_EVENT_TYPE_MANUAL) != 0) { + ast_destroy(ast); + return NULL; + } + + if (pthread_create(&ast->thread, NULL, video_thread, ast) != 0) { + ast_destroy(ast); + return NULL; + } + + ast->initialized = true; + + UNUSED_PARAMETER(settings); + UNUSED_PARAMETER(source); + return ast; +} + +struct obs_source_info async_sync_test = { + .id = "async_sync_test", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_ASYNC_VIDEO | + OBS_SOURCE_AUDIO, + .get_name = ast_getname, + .create = ast_create, + .destroy = ast_destroy, +}; diff --git a/test/test-input/sync-pair-aud.c b/test/test-input/sync-pair-aud.c new file mode 100644 index 000000000..52a66c18a --- /dev/null +++ b/test/test-input/sync-pair-aud.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include + +struct sync_pair_aud { + bool initialized_thread; + pthread_t thread; + os_event_t *event; + obs_source_t *source; +}; + +/* middle C */ +static const double rate = 261.63/48000.0; + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define M_PI_X2 M_PI*2 + +extern uint64_t starting_time; + +static inline bool whitelist_time(uint64_t ts, uint64_t interval, + uint64_t fps_num, uint64_t fps_den) +{ + if (!starting_time) + return false; + + uint64_t count = (ts - starting_time) / interval; + uint64_t sec = count * fps_den / fps_num; + return sec % 2 == 1; +} + +static void *sync_pair_aud_thread(void *pdata) +{ + struct sync_pair_aud *spa = pdata; + uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); + uint32_t frames = sample_rate / 100; + uint64_t last_time = obs_get_video_frame_time(); + double cos_val = 0.0; + float *samples = malloc(frames * sizeof(float)); + + uint64_t interval = video_output_get_frame_time(obs_get_video()); + const struct video_output_info *voi = + video_output_get_info(obs_get_video()); + uint64_t fps_num = voi->fps_num; + uint64_t fps_den = voi->fps_den; + + while (os_event_try(spa->event) == EAGAIN) { + if (!os_sleepto_ns(last_time += 10000000)) + last_time = obs_get_video_frame_time(); + + for (uint64_t i = 0; i < frames; i++) { + uint64_t ts = last_time + + i * 1000000000ULL / sample_rate; + + if (whitelist_time(ts, interval, fps_num, fps_den)) { + cos_val += rate * M_PI_X2; + if (cos_val > M_PI_X2) + cos_val -= M_PI_X2; + + samples[i] = (float)(cos(cos_val) * 0.5); + } else { + samples[i] = 0.0f; + } + } + + struct obs_source_audio data; + data.data[0] = (uint8_t*)samples; + data.frames = frames; + data.speakers = SPEAKERS_MONO; + data.samples_per_sec = sample_rate; + data.timestamp = last_time; + data.format = AUDIO_FORMAT_FLOAT; + obs_source_output_audio(spa->source, &data); + } + + free(samples); + + return NULL; +} + +/* ------------------------------------------------------------------------- */ + +static const char *sync_pair_aud_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return "Sync Test Pair (Audio)"; +} + +static void sync_pair_aud_destroy(void *data) +{ + struct sync_pair_aud *spa = data; + + if (spa) { + if (spa->initialized_thread) { + void *ret; + os_event_signal(spa->event); + pthread_join(spa->thread, &ret); + } + + os_event_destroy(spa->event); + bfree(spa); + } +} + +static void *sync_pair_aud_create(obs_data_t *settings, + obs_source_t *source) +{ + struct sync_pair_aud *spa = bzalloc(sizeof(struct sync_pair_aud)); + spa->source = source; + + if (os_event_init(&spa->event, OS_EVENT_TYPE_MANUAL) != 0) + goto fail; + if (pthread_create(&spa->thread, NULL, sync_pair_aud_thread, spa) != 0) + goto fail; + + spa->initialized_thread = true; + + UNUSED_PARAMETER(settings); + return spa; + +fail: + sync_pair_aud_destroy(spa); + return NULL; +} + +struct obs_source_info sync_audio = { + .id = "sync_audio", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_AUDIO, + .get_name = sync_pair_aud_getname, + .create = sync_pair_aud_create, + .destroy = sync_pair_aud_destroy, +}; diff --git a/test/test-input/sync-pair-vid.c b/test/test-input/sync-pair-vid.c new file mode 100644 index 000000000..ebfc32ca3 --- /dev/null +++ b/test/test-input/sync-pair-vid.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include + +struct sync_pair_vid { + obs_source_t *source; + gs_texture_t *tex; + gs_texture_t *white; + gs_texture_t *black; +}; + +uint64_t starting_time = 0; +uint64_t last_frame = 0; + +static const char *sync_pair_vid_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return "Sync Test Pair (Video)"; +} + +static void sync_pair_vid_destroy(void *data) +{ + struct sync_pair_vid *spv = data; + + obs_enter_graphics(); + gs_texture_destroy(spv->tex); + gs_texture_destroy(spv->white); + gs_texture_destroy(spv->black); + obs_leave_graphics(); + + bfree(spv); +} + +static inline void fill_texture(uint32_t *pixels, uint32_t pixel) +{ + size_t x, y; + + for (y = 0; y < 32; y++) { + for (x = 0; x < 32; x++) { + pixels[y*32 + x] = pixel; + } + } +} + +static void *sync_pair_vid_create(obs_data_t *settings, obs_source_t *source) +{ + struct sync_pair_vid *spv = bzalloc(sizeof(struct sync_pair_vid)); + spv->source = source; + + obs_enter_graphics(); + spv->tex = gs_texture_create(32, 32, GS_RGBA, 1, NULL, GS_DYNAMIC); + spv->white = gs_texture_create(32, 32, GS_RGBA, 1, NULL, GS_DYNAMIC); + spv->black = gs_texture_create(32, 32, GS_RGBA, 1, NULL, GS_DYNAMIC); + + uint8_t *ptr; + uint32_t linesize; + if (gs_texture_map(spv->white, &ptr, &linesize)) { + fill_texture((uint32_t*)ptr, 0xFFFFFFFF); + gs_texture_unmap(spv->white); + } + if (gs_texture_map(spv->black, &ptr, &linesize)) { + fill_texture((uint32_t*)ptr, 0xFF000000); + gs_texture_unmap(spv->black); + } + + obs_leave_graphics(); + + return spv; +} + +static inline bool whitelist_time(uint64_t ts, uint64_t interval, + uint64_t fps_num, uint64_t fps_den) +{ + if (!starting_time) + return false; + + uint64_t count = (ts - starting_time) / interval; + uint64_t sec = count * fps_den / fps_num; + return sec % 2 == 1; +} + +static void sync_pair_vid_render(void *data, gs_effect_t *effect) +{ + struct sync_pair_vid *spv = data; + + uint64_t ts = obs_get_video_frame_time(); + if (!starting_time) + starting_time = ts; + + uint64_t interval = video_output_get_frame_time(obs_get_video()); + const struct video_output_info *voi = + video_output_get_info(obs_get_video()); + uint64_t fps_num = voi->fps_num; + uint64_t fps_den = voi->fps_den; + + bool whitelist = whitelist_time(ts, interval, fps_num, fps_den); + +#if 0 + if (last_frame != ts) { + uint8_t *ptr; + uint32_t linesize; + if (gs_texture_map(spv->tex, &ptr, &linesize)) { + fill_texture((uint32_t*)ptr, whitelist ? 0xFFFFFFFF : 0xFF000000); + gs_texture_unmap(spv->tex); + } + last_frame = ts; + } + + obs_source_draw(spv->tex, 0, 0, 0, 0, 0); +#else + obs_source_draw(whitelist ? spv->white : spv->black, 0, 0, 0, 0, 0); +#endif +} + +static uint32_t sync_pair_vid_size(void *data) +{ + return 32; +} + +struct obs_source_info sync_video = { + .id = "sync_video", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = sync_pair_vid_getname, + .create = sync_pair_vid_create, + .destroy = sync_pair_vid_destroy, + .video_render = sync_pair_vid_render, + .get_width = sync_pair_vid_size, + .get_height = sync_pair_vid_size, +}; diff --git a/test/test-input/test-input.c b/test/test-input/test-input.c index 187398f9a..43f8c2438 100644 --- a/test/test-input/test-input.c +++ b/test/test-input/test-input.c @@ -5,11 +5,17 @@ OBS_DECLARE_MODULE() extern struct obs_source_info test_random; extern struct obs_source_info test_sinewave; extern struct obs_source_info test_filter; +extern struct obs_source_info async_sync_test; +extern struct obs_source_info sync_video; +extern struct obs_source_info sync_audio; bool obs_module_load(void) { obs_register_source(&test_random); obs_register_source(&test_sinewave); obs_register_source(&test_filter); + obs_register_source(&async_sync_test); + obs_register_source(&sync_video); + obs_register_source(&sync_audio); return true; }