|
@@ -46,6 +46,16 @@ struct video_input {
|
|
struct video_frame frame[MAX_CONVERT_BUFFERS];
|
|
struct video_frame frame[MAX_CONVERT_BUFFERS];
|
|
int cur_frame;
|
|
int cur_frame;
|
|
|
|
|
|
|
|
+ // allow outputting at fractions of main composition FPS,
|
|
|
|
+ // e.g. 60 FPS with frame_rate_divisor = 1 turns into 30 FPS
|
|
|
|
+ //
|
|
|
|
+ // a separate counter is used in favor of using remainder calculations
|
|
|
|
+ // to allow "inputs" started at the same time to start on the same frame
|
|
|
|
+ // whereas with remainder calculation the frame alignment would depend on
|
|
|
|
+ // the total frame count at the time the encoder was started
|
|
|
|
+ uint32_t frame_rate_divisor;
|
|
|
|
+ uint32_t frame_rate_divisor_counter;
|
|
|
|
+
|
|
void (*callback)(void *param, struct video_data *frame);
|
|
void (*callback)(void *param, struct video_data *frame);
|
|
void *param;
|
|
void *param;
|
|
};
|
|
};
|
|
@@ -77,6 +87,8 @@ struct video_output {
|
|
size_t last_added;
|
|
size_t last_added;
|
|
struct cached_frame_info cache[MAX_CACHE_SIZE];
|
|
struct cached_frame_info cache[MAX_CACHE_SIZE];
|
|
|
|
|
|
|
|
+ struct video_output *parent;
|
|
|
|
+
|
|
volatile bool raw_active;
|
|
volatile bool raw_active;
|
|
volatile long gpu_refs;
|
|
volatile long gpu_refs;
|
|
};
|
|
};
|
|
@@ -136,6 +148,17 @@ static inline bool video_output_cur_frame(struct video_output *video)
|
|
struct video_input *input = video->inputs.array + i;
|
|
struct video_input *input = video->inputs.array + i;
|
|
struct video_data frame = frame_info->frame;
|
|
struct video_data frame = frame_info->frame;
|
|
|
|
|
|
|
|
+ // an explicit counter is used instead of remainder calculation
|
|
|
|
+ // to allow multiple encoders started at the same time to start on
|
|
|
|
+ // the same frame
|
|
|
|
+ uint32_t skip = input->frame_rate_divisor_counter++;
|
|
|
|
+ if (input->frame_rate_divisor_counter ==
|
|
|
|
+ input->frame_rate_divisor)
|
|
|
|
+ input->frame_rate_divisor_counter = 0;
|
|
|
|
+
|
|
|
|
+ if (skip)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
if (scale_video_output(input, &frame))
|
|
if (scale_video_output(input, &frame))
|
|
input->callback(input->param, &frame);
|
|
input->callback(input->param, &frame);
|
|
}
|
|
}
|
|
@@ -371,13 +394,37 @@ static inline void reset_frames(video_t *video)
|
|
os_atomic_set_long(&video->total_frames, 0);
|
|
os_atomic_set_long(&video->total_frames, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static const video_t *get_const_root(const video_t *video)
|
|
|
|
+{
|
|
|
|
+ while (video->parent)
|
|
|
|
+ video = video->parent;
|
|
|
|
+ return video;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static video_t *get_root(video_t *video)
|
|
|
|
+{
|
|
|
|
+ while (video->parent)
|
|
|
|
+ video = video->parent;
|
|
|
|
+ return video;
|
|
|
|
+}
|
|
|
|
+
|
|
bool video_output_connect(
|
|
bool video_output_connect(
|
|
video_t *video, const struct video_scale_info *conversion,
|
|
video_t *video, const struct video_scale_info *conversion,
|
|
void (*callback)(void *param, struct video_data *frame), void *param)
|
|
void (*callback)(void *param, struct video_data *frame), void *param)
|
|
|
|
+{
|
|
|
|
+ return video_output_connect2(video, conversion, 1, callback, param);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool video_output_connect2(
|
|
|
|
+ video_t *video, const struct video_scale_info *conversion,
|
|
|
|
+ uint32_t frame_rate_divisor,
|
|
|
|
+ void (*callback)(void *param, struct video_data *frame), void *param)
|
|
{
|
|
{
|
|
bool success = false;
|
|
bool success = false;
|
|
|
|
|
|
- if (!video || !callback)
|
|
|
|
|
|
+ video = get_root(video);
|
|
|
|
+
|
|
|
|
+ if (!video || !callback || frame_rate_divisor == 0)
|
|
return false;
|
|
return false;
|
|
|
|
|
|
pthread_mutex_lock(&video->input_mutex);
|
|
pthread_mutex_lock(&video->input_mutex);
|
|
@@ -389,6 +436,8 @@ bool video_output_connect(
|
|
input.callback = callback;
|
|
input.callback = callback;
|
|
input.param = param;
|
|
input.param = param;
|
|
|
|
|
|
|
|
+ input.frame_rate_divisor = frame_rate_divisor;
|
|
|
|
+
|
|
if (conversion) {
|
|
if (conversion) {
|
|
input.conversion = *conversion;
|
|
input.conversion = *conversion;
|
|
} else {
|
|
} else {
|
|
@@ -446,6 +495,8 @@ void video_output_disconnect(video_t *video,
|
|
if (!video || !callback)
|
|
if (!video || !callback)
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
+ video = get_root(video);
|
|
|
|
+
|
|
pthread_mutex_lock(&video->input_mutex);
|
|
pthread_mutex_lock(&video->input_mutex);
|
|
|
|
|
|
size_t idx = video_get_input_idx(video, callback, param);
|
|
size_t idx = video_get_input_idx(video, callback, param);
|
|
@@ -468,7 +519,7 @@ bool video_output_active(const video_t *video)
|
|
{
|
|
{
|
|
if (!video)
|
|
if (!video)
|
|
return false;
|
|
return false;
|
|
- return os_atomic_load_bool(&video->raw_active);
|
|
|
|
|
|
+ return os_atomic_load_bool(&get_const_root(video)->raw_active);
|
|
}
|
|
}
|
|
|
|
|
|
const struct video_output_info *video_output_get_info(const video_t *video)
|
|
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,
|
|
if (!video)
|
|
if (!video)
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
+ video = get_root(video);
|
|
|
|
+
|
|
pthread_mutex_lock(&video->data_mutex);
|
|
pthread_mutex_lock(&video->data_mutex);
|
|
|
|
|
|
if (video->available_frames == 0) {
|
|
if (video->available_frames == 0) {
|
|
@@ -518,6 +571,8 @@ void video_output_unlock_frame(video_t *video)
|
|
if (!video)
|
|
if (!video)
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
+ video = get_root(video);
|
|
|
|
+
|
|
pthread_mutex_lock(&video->data_mutex);
|
|
pthread_mutex_lock(&video->data_mutex);
|
|
|
|
|
|
video->available_frames--;
|
|
video->available_frames--;
|
|
@@ -538,6 +593,8 @@ void video_output_stop(video_t *video)
|
|
if (!video)
|
|
if (!video)
|
|
return;
|
|
return;
|
|
|
|
|
|
|
|
+ video = get_root(video);
|
|
|
|
+
|
|
if (!video->stop) {
|
|
if (!video->stop) {
|
|
video->stop = true;
|
|
video->stop = true;
|
|
os_sem_post(video->update_semaphore);
|
|
os_sem_post(video->update_semaphore);
|
|
@@ -550,22 +607,22 @@ bool video_output_stopped(video_t *video)
|
|
if (!video)
|
|
if (!video)
|
|
return true;
|
|
return true;
|
|
|
|
|
|
- return video->stop;
|
|
|
|
|
|
+ return get_root(video)->stop;
|
|
}
|
|
}
|
|
|
|
|
|
enum video_format video_output_get_format(const video_t *video)
|
|
enum video_format video_output_get_format(const video_t *video)
|
|
{
|
|
{
|
|
- return video ? video->info.format : VIDEO_FORMAT_NONE;
|
|
|
|
|
|
+ return video ? get_const_root(video)->info.format : VIDEO_FORMAT_NONE;
|
|
}
|
|
}
|
|
|
|
|
|
uint32_t video_output_get_width(const video_t *video)
|
|
uint32_t video_output_get_width(const video_t *video)
|
|
{
|
|
{
|
|
- return video ? video->info.width : 0;
|
|
|
|
|
|
+ return video ? get_const_root(video)->info.width : 0;
|
|
}
|
|
}
|
|
|
|
|
|
uint32_t video_output_get_height(const video_t *video)
|
|
uint32_t video_output_get_height(const video_t *video)
|
|
{
|
|
{
|
|
- return video ? video->info.height : 0;
|
|
|
|
|
|
+ return video ? get_const_root(video)->info.height : 0;
|
|
}
|
|
}
|
|
|
|
|
|
double video_output_get_frame_rate(const video_t *video)
|
|
double video_output_get_frame_rate(const video_t *video)
|
|
@@ -573,17 +630,21 @@ double video_output_get_frame_rate(const video_t *video)
|
|
if (!video)
|
|
if (!video)
|
|
return 0.0;
|
|
return 0.0;
|
|
|
|
|
|
|
|
+ video = get_const_root(video);
|
|
|
|
+
|
|
return (double)video->info.fps_num / (double)video->info.fps_den;
|
|
return (double)video->info.fps_num / (double)video->info.fps_den;
|
|
}
|
|
}
|
|
|
|
|
|
uint32_t video_output_get_skipped_frames(const video_t *video)
|
|
uint32_t video_output_get_skipped_frames(const video_t *video)
|
|
{
|
|
{
|
|
- return (uint32_t)os_atomic_load_long(&video->skipped_frames);
|
|
|
|
|
|
+ return (uint32_t)os_atomic_load_long(
|
|
|
|
+ &get_const_root(video)->skipped_frames);
|
|
}
|
|
}
|
|
|
|
|
|
uint32_t video_output_get_total_frames(const video_t *video)
|
|
uint32_t video_output_get_total_frames(const video_t *video)
|
|
{
|
|
{
|
|
- return (uint32_t)os_atomic_load_long(&video->total_frames);
|
|
|
|
|
|
+ return (uint32_t)os_atomic_load_long(
|
|
|
|
+ &get_const_root(video)->total_frames);
|
|
}
|
|
}
|
|
|
|
|
|
/* Note: These four functions below are a very slight bit of a hack. If the
|
|
/* 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)
|
|
|
|
|
|
void video_output_inc_texture_encoders(video_t *video)
|
|
void video_output_inc_texture_encoders(video_t *video)
|
|
{
|
|
{
|
|
|
|
+ video = get_root(video);
|
|
|
|
+
|
|
if (os_atomic_inc_long(&video->gpu_refs) == 1 &&
|
|
if (os_atomic_inc_long(&video->gpu_refs) == 1 &&
|
|
!os_atomic_load_bool(&video->raw_active)) {
|
|
!os_atomic_load_bool(&video->raw_active)) {
|
|
reset_frames(video);
|
|
reset_frames(video);
|
|
@@ -602,6 +665,8 @@ void video_output_inc_texture_encoders(video_t *video)
|
|
|
|
|
|
void video_output_dec_texture_encoders(video_t *video)
|
|
void video_output_dec_texture_encoders(video_t *video)
|
|
{
|
|
{
|
|
|
|
+ video = get_root(video);
|
|
|
|
+
|
|
if (os_atomic_dec_long(&video->gpu_refs) == 0 &&
|
|
if (os_atomic_dec_long(&video->gpu_refs) == 0 &&
|
|
!os_atomic_load_bool(&video->raw_active)) {
|
|
!os_atomic_load_bool(&video->raw_active)) {
|
|
log_skipped(video);
|
|
log_skipped(video);
|
|
@@ -610,10 +675,32 @@ void video_output_dec_texture_encoders(video_t *video)
|
|
|
|
|
|
void video_output_inc_texture_frames(video_t *video)
|
|
void video_output_inc_texture_frames(video_t *video)
|
|
{
|
|
{
|
|
- os_atomic_inc_long(&video->total_frames);
|
|
|
|
|
|
+ os_atomic_inc_long(&get_root(video)->total_frames);
|
|
}
|
|
}
|
|
|
|
|
|
void video_output_inc_texture_skipped_frames(video_t *video)
|
|
void video_output_inc_texture_skipped_frames(video_t *video)
|
|
{
|
|
{
|
|
- os_atomic_inc_long(&video->skipped_frames);
|
|
|
|
|
|
+ os_atomic_inc_long(&get_root(video)->skipped_frames);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+video_t *video_output_create_with_frame_rate_divisor(video_t *video,
|
|
|
|
+ uint32_t divisor)
|
|
|
|
+{
|
|
|
|
+ // `divisor == 1` would result in the same frame rate,
|
|
|
|
+ // resulting in an unnecessary additional video output
|
|
|
|
+ if (!video || divisor == 0 || divisor == 1)
|
|
|
|
+ return NULL;
|
|
|
|
+
|
|
|
|
+ video_t *new_video = bzalloc(sizeof(video_t));
|
|
|
|
+ memcpy(new_video, video, sizeof(*new_video));
|
|
|
|
+ new_video->parent = video;
|
|
|
|
+ new_video->info.fps_den *= divisor;
|
|
|
|
+
|
|
|
|
+ return new_video;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void video_output_free_frame_rate_divisor(video_t *video)
|
|
|
|
+{
|
|
|
|
+ if (video && video->parent)
|
|
|
|
+ bfree(video);
|
|
}
|
|
}
|