test: Add sync tests

The async source sync test verifies that async sources are playing back
in sync properly, and the "sync pair (video)" and "sync pair (audio)"
sources test the back-end syncing down to the exact sample via inserting
audio data directly in to the back-end with system timestamps.

For these sources, when the video is white, a C sinewave should be
playing, and when the video is black audio is mute.
master
jp9000 2017-09-28 14:58:53 -07:00
parent 5b20942247
commit 292a6e59f4
5 changed files with 421 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1,143 @@
#include <stdlib.h>
#include <util/threading.h>
#include <util/platform.h>
#include <obs.h>
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,
};

View File

@ -0,0 +1,137 @@
#include <math.h>
#include <util/bmem.h>
#include <util/threading.h>
#include <util/platform.h>
#include <obs.h>
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,
};

View File

@ -0,0 +1,132 @@
#include <stdlib.h>
#include <util/threading.h>
#include <util/platform.h>
#include <graphics/graphics.h>
#include <obs.h>
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,
};

View File

@ -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;
}