Skip to content

Commit 6cdfb0a

Browse files
palanaLain-B
authored andcommitted
libobs: Allow configuring frame rate divisor for encoders
This allows encoders/outputs to output at a frame rate that is lower than the configured base frame rate
1 parent 290570b commit 6cdfb0a

8 files changed

Lines changed: 248 additions & 19 deletions

File tree

libobs/media-io/video-io.c

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ struct video_input {
4646
struct video_frame frame[MAX_CONVERT_BUFFERS];
4747
int cur_frame;
4848

49+
// allow outputting at fractions of main composition FPS,
50+
// e.g. 60 FPS with frame_rate_divisor = 1 turns into 30 FPS
51+
//
52+
// a separate counter is used in favor of using remainder calculations
53+
// to allow "inputs" started at the same time to start on the same frame
54+
// whereas with remainder calculation the frame alignment would depend on
55+
// the total frame count at the time the encoder was started
56+
uint32_t frame_rate_divisor;
57+
uint32_t frame_rate_divisor_counter;
58+
4959
void (*callback)(void *param, struct video_data *frame);
5060
void *param;
5161
};
@@ -77,6 +87,8 @@ struct video_output {
7787
size_t last_added;
7888
struct cached_frame_info cache[MAX_CACHE_SIZE];
7989

90+
struct video_output *parent;
91+
8092
volatile bool raw_active;
8193
volatile long gpu_refs;
8294
};
@@ -136,6 +148,17 @@ static inline bool video_output_cur_frame(struct video_output *video)
136148
struct video_input *input = video->inputs.array + i;
137149
struct video_data frame = frame_info->frame;
138150

151+
// an explicit counter is used instead of remainder calculation
152+
// to allow multiple encoders started at the same time to start on
153+
// the same frame
154+
uint32_t skip = input->frame_rate_divisor_counter++;
155+
if (input->frame_rate_divisor_counter ==
156+
input->frame_rate_divisor)
157+
input->frame_rate_divisor_counter = 0;
158+
159+
if (skip)
160+
continue;
161+
139162
if (scale_video_output(input, &frame))
140163
input->callback(input->param, &frame);
141164
}
@@ -371,13 +394,37 @@ static inline void reset_frames(video_t *video)
371394
os_atomic_set_long(&video->total_frames, 0);
372395
}
373396

397+
static const video_t *get_const_root(const video_t *video)
398+
{
399+
while (video->parent)
400+
video = video->parent;
401+
return video;
402+
}
403+
404+
static video_t *get_root(video_t *video)
405+
{
406+
while (video->parent)
407+
video = video->parent;
408+
return video;
409+
}
410+
374411
bool video_output_connect(
375412
video_t *video, const struct video_scale_info *conversion,
376413
void (*callback)(void *param, struct video_data *frame), void *param)
414+
{
415+
return video_output_connect2(video, conversion, 1, callback, param);
416+
}
417+
418+
bool video_output_connect2(
419+
video_t *video, const struct video_scale_info *conversion,
420+
uint32_t frame_rate_divisor,
421+
void (*callback)(void *param, struct video_data *frame), void *param)
377422
{
378423
bool success = false;
379424

380-
if (!video || !callback)
425+
video = get_root(video);
426+
427+
if (!video || !callback || frame_rate_divisor == 0)
381428
return false;
382429

383430
pthread_mutex_lock(&video->input_mutex);
@@ -389,6 +436,8 @@ bool video_output_connect(
389436
input.callback = callback;
390437
input.param = param;
391438

439+
input.frame_rate_divisor = frame_rate_divisor;
440+
392441
if (conversion) {
393442
input.conversion = *conversion;
394443
} else {
@@ -446,6 +495,8 @@ void video_output_disconnect(video_t *video,
446495
if (!video || !callback)
447496
return;
448497

498+
video = get_root(video);
499+
449500
pthread_mutex_lock(&video->input_mutex);
450501

451502
size_t idx = video_get_input_idx(video, callback, param);
@@ -468,7 +519,7 @@ bool video_output_active(const video_t *video)
468519
{
469520
if (!video)
470521
return false;
471-
return os_atomic_load_bool(&video->raw_active);
522+
return os_atomic_load_bool(&get_const_root(video)->raw_active);
472523
}
473524

474525
const struct video_output_info *video_output_get_info(const video_t *video)
@@ -485,6 +536,8 @@ bool video_output_lock_frame(video_t *video, struct video_frame *frame,
485536
if (!video)
486537
return false;
487538

539+
video = get_root(video);
540+
488541
pthread_mutex_lock(&video->data_mutex);
489542

490543
if (video->available_frames == 0) {
@@ -518,6 +571,8 @@ void video_output_unlock_frame(video_t *video)
518571
if (!video)
519572
return;
520573

574+
video = get_root(video);
575+
521576
pthread_mutex_lock(&video->data_mutex);
522577

523578
video->available_frames--;
@@ -538,6 +593,8 @@ void video_output_stop(video_t *video)
538593
if (!video)
539594
return;
540595

596+
video = get_root(video);
597+
541598
if (!video->stop) {
542599
video->stop = true;
543600
os_sem_post(video->update_semaphore);
@@ -550,40 +607,44 @@ bool video_output_stopped(video_t *video)
550607
if (!video)
551608
return true;
552609

553-
return video->stop;
610+
return get_root(video)->stop;
554611
}
555612

556613
enum video_format video_output_get_format(const video_t *video)
557614
{
558-
return video ? video->info.format : VIDEO_FORMAT_NONE;
615+
return video ? get_const_root(video)->info.format : VIDEO_FORMAT_NONE;
559616
}
560617

561618
uint32_t video_output_get_width(const video_t *video)
562619
{
563-
return video ? video->info.width : 0;
620+
return video ? get_const_root(video)->info.width : 0;
564621
}
565622

566623
uint32_t video_output_get_height(const video_t *video)
567624
{
568-
return video ? video->info.height : 0;
625+
return video ? get_const_root(video)->info.height : 0;
569626
}
570627

571628
double video_output_get_frame_rate(const video_t *video)
572629
{
573630
if (!video)
574631
return 0.0;
575632

633+
video = get_const_root(video);
634+
576635
return (double)video->info.fps_num / (double)video->info.fps_den;
577636
}
578637

579638
uint32_t video_output_get_skipped_frames(const video_t *video)
580639
{
581-
return (uint32_t)os_atomic_load_long(&video->skipped_frames);
640+
return (uint32_t)os_atomic_load_long(
641+
&get_const_root(video)->skipped_frames);
582642
}
583643

584644
uint32_t video_output_get_total_frames(const video_t *video)
585645
{
586-
return (uint32_t)os_atomic_load_long(&video->total_frames);
646+
return (uint32_t)os_atomic_load_long(
647+
&get_const_root(video)->total_frames);
587648
}
588649

589650
/* Note: These four functions below are a very slight bit of a hack. If the
@@ -594,6 +655,8 @@ uint32_t video_output_get_total_frames(const video_t *video)
594655

595656
void video_output_inc_texture_encoders(video_t *video)
596657
{
658+
video = get_root(video);
659+
597660
if (os_atomic_inc_long(&video->gpu_refs) == 1 &&
598661
!os_atomic_load_bool(&video->raw_active)) {
599662
reset_frames(video);
@@ -602,6 +665,8 @@ void video_output_inc_texture_encoders(video_t *video)
602665

603666
void video_output_dec_texture_encoders(video_t *video)
604667
{
668+
video = get_root(video);
669+
605670
if (os_atomic_dec_long(&video->gpu_refs) == 0 &&
606671
!os_atomic_load_bool(&video->raw_active)) {
607672
log_skipped(video);
@@ -610,10 +675,32 @@ void video_output_dec_texture_encoders(video_t *video)
610675

611676
void video_output_inc_texture_frames(video_t *video)
612677
{
613-
os_atomic_inc_long(&video->total_frames);
678+
os_atomic_inc_long(&get_root(video)->total_frames);
614679
}
615680

616681
void video_output_inc_texture_skipped_frames(video_t *video)
617682
{
618-
os_atomic_inc_long(&video->skipped_frames);
683+
os_atomic_inc_long(&get_root(video)->skipped_frames);
684+
}
685+
686+
video_t *video_output_create_with_frame_rate_divisor(video_t *video,
687+
uint32_t divisor)
688+
{
689+
// `divisor == 1` would result in the same frame rate,
690+
// resulting in an unnecessary additional video output
691+
if (!video || divisor == 0 || divisor == 1)
692+
return NULL;
693+
694+
video_t *new_video = bzalloc(sizeof(video_t));
695+
memcpy(new_video, video, sizeof(*new_video));
696+
new_video->parent = video;
697+
new_video->info.fps_den *= divisor;
698+
699+
return new_video;
700+
}
701+
702+
void video_output_free_frame_rate_divisor(video_t *video)
703+
{
704+
if (video && video->parent)
705+
bfree(video);
619706
}

libobs/media-io/video-io.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ EXPORT bool
302302
video_output_connect(video_t *video, const struct video_scale_info *conversion,
303303
void (*callback)(void *param, struct video_data *frame),
304304
void *param);
305+
EXPORT bool
306+
video_output_connect2(video_t *video, const struct video_scale_info *conversion,
307+
uint32_t frame_rate_divisor,
308+
void (*callback)(void *param, struct video_data *frame),
309+
void *param);
305310
EXPORT void video_output_disconnect(video_t *video,
306311
void (*callback)(void *param,
307312
struct video_data *frame),
@@ -331,6 +336,10 @@ extern void video_output_dec_texture_encoders(video_t *video);
331336
extern void video_output_inc_texture_frames(video_t *video);
332337
extern void video_output_inc_texture_skipped_frames(video_t *video);
333338

339+
extern video_t *video_output_create_with_frame_rate_divisor(video_t *video,
340+
uint32_t divisor);
341+
extern void video_output_free_frame_rate_divisor(video_t *video);
342+
334343
#ifdef __cplusplus
335344
}
336345
#endif

0 commit comments

Comments
 (0)