libobs: Buffer scene item visibility actions

This buffers scene item visibility actions so that if
obs_sceneitem_set_visible to true or false, that it will ensure that the
action is mapped to the exact sample point time in which
obs_sceneitem_set_visible is called.  Mapping to the exact sample point
isn't necessary, but it's a nice thing to have.
This commit is contained in:
jp9000 2015-12-25 01:01:12 -08:00
parent 247a42c2a1
commit 3c68196c5c
2 changed files with 215 additions and 26 deletions

View file

@ -142,7 +142,7 @@ static void scene_enum_sources(void *data,
next = item->next;
obs_sceneitem_addref(item);
if (item->visible)
if (os_atomic_load_long(&item->active_refs) > 0)
enum_callback(scene->source, item->source, param);
obs_sceneitem_release(item);
@ -347,7 +347,7 @@ static void scene_video_render(void *data, gs_effect_t *effect)
if (source_size_changed(item))
update_item_transform(item);
if (item->visible) {
if (item->user_visible) {
gs_matrix_push();
gs_matrix_mul(&item->draw_transform);
obs_source_video_render(item->source);
@ -364,11 +364,33 @@ static void scene_video_render(void *data, gs_effect_t *effect)
UNUSED_PARAMETER(effect);
}
static inline void set_visibility(struct obs_scene_item *item, bool vis)
{
pthread_mutex_lock(&item->actions_mutex);
da_resize(item->audio_actions, 0);
if (os_atomic_load_long(&item->active_refs) > 0) {
if (!vis)
obs_source_remove_active_child(item->parent->source,
item->source);
} else if (vis) {
obs_source_add_active_child(item->parent->source, item->source);
}
os_atomic_set_long(&item->active_refs, vis ? 1 : 0);
item->visible = vis;
item->user_visible = vis;
pthread_mutex_unlock(&item->actions_mutex);
}
static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
{
const char *name = obs_data_get_string(item_data, "name");
obs_source_t *source = obs_get_source_by_name(name);
struct obs_scene_item *item;
bool visible;
if (!source) {
blog(LOG_WARNING, "[scene_load_item] Source %s not found!",
@ -391,12 +413,11 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
item->rot = (float)obs_data_get_double(item_data, "rot");
item->align = (uint32_t)obs_data_get_int(item_data, "align");
item->visible = obs_data_get_bool(item_data, "visible");
visible = obs_data_get_bool(item_data, "visible");
obs_data_get_vec2(item_data, "pos", &item->pos);
obs_data_get_vec2(item_data, "scale", &item->scale);
if (!item->visible)
obs_source_remove_active_child(scene->source, source);
set_visibility(item, visible);
item->bounds_type =
(enum obs_bounds_type)obs_data_get_int(item_data,
@ -437,7 +458,7 @@ static void scene_save_item(obs_data_array_t *array,
const char *name = obs_source_get_name(item->source);
obs_data_set_string(item_data, "name", name);
obs_data_set_bool (item_data, "visible", item->visible);
obs_data_set_bool (item_data, "visible", item->user_visible);
obs_data_set_double(item_data, "rot", item->rot);
obs_data_set_vec2 (item_data, "pos", &item->pos);
obs_data_set_vec2 (item_data, "scale", &item->scale);
@ -482,6 +503,101 @@ static uint32_t scene_getheight(void *data)
return obs->video.base_height;
}
static void apply_scene_item_audio_actions(struct obs_scene_item *item,
float **p_buf, uint64_t ts, size_t sample_rate)
{
bool cur_visible = item->visible;
uint64_t frame_num = 0;
size_t deref_count = 0;
float *buf;
if (!*p_buf)
*p_buf = malloc(AUDIO_OUTPUT_FRAMES * sizeof(float));
buf = *p_buf;
pthread_mutex_lock(&item->actions_mutex);
for (size_t i = 0; i < item->audio_actions.num; i++) {
struct item_action action = item->audio_actions.array[i];
uint64_t timestamp = action.timestamp;
uint64_t new_frame_num;
if (timestamp < ts)
timestamp = ts;
new_frame_num = (timestamp - ts) * (uint64_t)sample_rate /
1000000000ULL;
if (new_frame_num >= AUDIO_OUTPUT_FRAMES)
break;
da_erase(item->audio_actions, i--);
item->visible = action.visible;
if (!item->visible)
deref_count++;
if (new_frame_num > frame_num) {
for (; frame_num < new_frame_num; frame_num++)
buf[frame_num] = cur_visible ? 1.0f : 0.0f;
}
cur_visible = item->visible;
}
for (; frame_num < AUDIO_OUTPUT_FRAMES; frame_num++)
buf[frame_num] = cur_visible ? 1.0f : 0.0f;
pthread_mutex_unlock(&item->actions_mutex);
while (deref_count--) {
if (os_atomic_dec_long(&item->active_refs) == 0) {
obs_source_remove_active_child(item->parent->source,
item->source);
}
}
}
static inline bool apply_scene_item_volume(struct obs_scene_item *item,
float **buf, uint64_t ts, size_t sample_rate)
{
bool actions_pending;
struct item_action action;
pthread_mutex_lock(&item->actions_mutex);
actions_pending = item->audio_actions.num > 0;
if (actions_pending)
action = item->audio_actions.array[0];
pthread_mutex_unlock(&item->actions_mutex);
if (actions_pending) {
uint64_t duration = (uint64_t)AUDIO_OUTPUT_FRAMES *
1000000000ULL / (uint64_t)sample_rate;
if (action.timestamp < (ts + duration)) {
apply_scene_item_audio_actions(item, buf, ts,
sample_rate);
return true;
}
}
return false;
}
static void mix_audio_with_buf(float *p_out, float *p_in, float *buf_in,
size_t pos, size_t count)
{
register float *out = p_out;
register float *buf = buf_in + pos;
register float *in = p_in + pos;
register float *end = in + count;
while (in < end)
*(out++) += *(in++) * *(buf++);
}
static inline void mix_audio(float *p_out, float *p_in,
size_t pos, size_t count)
{
@ -498,6 +614,7 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
size_t channels, size_t sample_rate)
{
uint64_t timestamp = 0;
float *buf = NULL;
struct obs_source_audio_mix child_audio;
struct obs_scene *scene = data;
struct obs_scene_item *item;
@ -526,6 +643,10 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
while (item) {
uint64_t source_ts;
size_t pos, count;
bool apply_buf;
apply_buf = apply_scene_item_volume(item, &buf, timestamp,
sample_rate);
if (obs_source_audio_pending(item->source)) {
item = item->next;
@ -537,13 +658,25 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
source_ts - timestamp);
count = AUDIO_OUTPUT_FRAMES - pos;
if (!apply_buf && !item->visible) {
item = item->next;
continue;
}
obs_source_get_audio_mix(item->source, &child_audio);
for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
if ((mixers & (1 << mix)) == 0)
continue;
for (size_t ch = 0; ch < channels; ch++) {
float *out = audio_output->output[mix].data[ch];
float *in = child_audio.output[mix].data[ch];
mix_audio(out, in, pos, count);
if (apply_buf)
mix_audio_with_buf(out, in, buf, pos,
count);
else
mix_audio(out, in, pos, count);
}
}
@ -552,6 +685,8 @@ static bool scene_audio_render(void *data, uint64_t *ts_out,
*ts_out = timestamp;
audio_unlock(scene);
free(buf);
return true;
}
@ -596,10 +731,13 @@ obs_scene_t *obs_scene_duplicate(obs_scene_t *scene, const char *name)
struct obs_scene_item *new_item =
obs_scene_add(new_scene, source);
new_item->visible = item->visible;
if (!new_item->visible)
obs_source_remove_active_child(
new_scene->source, source);
if (!new_item) {
item = item->next;
continue;
}
if (!item->user_visible)
set_visibility(new_item, false);
new_item->selected = item->selected;
new_item->pos = item->pos;
@ -718,7 +856,7 @@ static bool hotkey_show_sceneitem(void *data, obs_hotkey_pair_id id,
UNUSED_PARAMETER(hotkey);
obs_sceneitem_t *si = sceneitem_get_ref(data);
if (pressed && si && !si->visible) {
if (pressed && si && !si->user_visible) {
obs_sceneitem_set_visible(si, true);
obs_sceneitem_release(si);
return true;
@ -735,7 +873,7 @@ static bool hotkey_hide_sceneitem(void *data, obs_hotkey_pair_id id,
UNUSED_PARAMETER(hotkey);
obs_sceneitem_t *si = sceneitem_get_ref(data);
if (pressed && si && si->visible) {
if (pressed && si && si->user_visible) {
obs_sceneitem_set_visible(si, false);
obs_sceneitem_release(si);
return true;
@ -775,11 +913,23 @@ static void init_hotkeys(obs_scene_t *scene, obs_sceneitem_t *item,
dstr_free(&hide_desc);
}
static inline bool source_has_audio(obs_source_t *source)
{
return (source->info.output_flags &
(OBS_SOURCE_AUDIO | OBS_SOURCE_COMPOSITE)) != 0;
}
obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source)
{
struct obs_scene_item *last;
struct obs_scene_item *item;
struct calldata params = {0};
pthread_mutex_t mutex;
struct item_action action = {
.visible = true,
.timestamp = os_gettime_ns()
};
if (!scene)
return NULL;
@ -789,24 +939,39 @@ obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source)
return NULL;
}
if (pthread_mutex_init(&mutex, NULL) != 0) {
blog(LOG_WARNING, "Failed to create scene item mutex");
return NULL;
}
if (!obs_source_add_active_child(scene->source, source)) {
blog(LOG_WARNING, "Failed to add source to scene due to "
"infinite source recursion");
pthread_mutex_destroy(&mutex);
return NULL;
}
item = bzalloc(sizeof(struct obs_scene_item));
item->source = source;
item->visible = true;
item->parent = scene;
item->ref = 1;
item->align = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
item->actions_mutex = mutex;
item->user_visible = true;
os_atomic_set_long(&item->active_refs, 1);
vec2_set(&item->scale, 1.0f, 1.0f);
matrix4_identity(&item->draw_transform);
matrix4_identity(&item->box_transform);
obs_source_addref(source);
if (source_has_audio(source)) {
item->visible = false;
da_push_back(item->audio_actions, &action);
} else {
item->visible = true;
}
full_lock(scene);
last = scene->first_item;
@ -837,8 +1002,10 @@ static void obs_sceneitem_destroy(obs_sceneitem_t *item)
{
if (item) {
obs_hotkey_pair_unregister(item->toggle_visibility);
pthread_mutex_destroy(&item->actions_mutex);
if (item->source)
obs_source_release(item->source);
da_free(item->audio_actions);
bfree(item);
}
}
@ -880,8 +1047,8 @@ void obs_sceneitem_remove(obs_sceneitem_t *item)
assert(scene != NULL);
assert(scene->source != NULL);
if (item->visible)
obs_source_remove_active_child(scene->source, item->source);
set_visibility(item, false);
signal_item_remove(item);
detach_sceneitem(item);
@ -1155,32 +1322,37 @@ void obs_sceneitem_get_box_transform(const obs_sceneitem_t *item,
bool obs_sceneitem_visible(const obs_sceneitem_t *item)
{
return item ? item->visible : false;
return item ? item->user_visible : false;
}
bool obs_sceneitem_set_visible(obs_sceneitem_t *item, bool visible)
{
struct calldata cd = {0};
struct item_action action = {
.visible = visible,
.timestamp = os_gettime_ns()
};
if (!item)
return false;
if (item->visible == visible)
if (item->user_visible == visible)
return false;
if (!item->parent)
return false;
if (visible) {
if (!obs_source_add_active_child(item->parent->source,
item->source))
return false;
} else {
obs_source_remove_active_child(item->parent->source,
item->source);
if (os_atomic_inc_long(&item->active_refs) == 1) {
if (!obs_source_add_active_child(item->parent->source,
item->source)) {
os_atomic_dec_long(&item->active_refs);
return false;
}
}
}
item->visible = visible;
item->user_visible = visible;
calldata_set_ptr(&cd, "scene", item->parent);
calldata_set_ptr(&cd, "item", item);
@ -1188,8 +1360,15 @@ bool obs_sceneitem_set_visible(obs_sceneitem_t *item, bool visible)
signal_handler_signal(item->parent->source->context.signals,
"item_visible", &cd);
calldata_free(&cd);
if (source_has_audio(item->source)) {
pthread_mutex_lock(&item->actions_mutex);
da_push_back(item->audio_actions, &action);
pthread_mutex_unlock(&item->actions_mutex);
} else {
set_visibility(item, visible);
}
return true;
}

View file

@ -23,12 +23,19 @@
/* how obs scene! */
struct item_action {
bool visible;
uint64_t timestamp;
};
struct obs_scene_item {
volatile long ref;
volatile bool removed;
struct obs_scene *parent;
struct obs_source *source;
volatile long active_refs;
bool user_visible;
bool visible;
bool selected;
@ -51,6 +58,9 @@ struct obs_scene_item {
obs_hotkey_pair_id toggle_visibility;
pthread_mutex_t actions_mutex;
DARRAY(struct item_action) audio_actions;
/* would do **prev_next, but not really great for reordering */
struct obs_scene_item *prev;
struct obs_scene_item *next;