Skip to content

Commit 24f60a6

Browse files
committed
add vcap/add_frame
this allows converting 50p to 60p or even 50i to 60p: `uv -F double_framerate:nodelay,add_frame:e=5 -t testcard:fps=50i` Another change is that from capture_filter dropped early return if nullptr returned - this will preclude the above complex example because the "flushing" nullptr would be consumed by double_framerate and additional frames won't be read from add_frame
1 parent 352959f commit 24f60a6

3 files changed

Lines changed: 174 additions & 2 deletions

File tree

‎configure.ac‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3639,6 +3639,7 @@ if test "$build_default" != no || test "$req_files" = all; then
36393639
src/audio/filter/silence.o
36403640
src/audio/filter/playback.o
36413641
src/audio/filter/channel_remap.o
3642+
src/capture_filter/add_frame.o
36423643
src/capture_filter/change_pixfmt.o
36433644
src/capture_filter/color.o
36443645
src/capture_filter/display.o

‎src/capture_filter.cpp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,6 @@ struct video_frame *capture_filter(struct capture_filter *state, struct video_fr
221221
) {
222222
struct capture_filter_instance *inst = (struct capture_filter_instance *) simple_linked_list_it_next(&it);
223223
frame = inst->functions->filter(inst->state, frame);
224-
if(!frame)
225-
return NULL;
226224
}
227225
return frame;
228226
}

‎src/capture_filter/add_frame.c‎

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// Copyright (c) 2026 CESNET, zájmové sdružení právických osob
3+
4+
#include <assert.h> // for assert
5+
#include <stdint.h> // for uint32_t
6+
#include <stdlib.h> // for free, atoi, calloc
7+
#include <string.h> // for memcpy, strchr, strcmp, strdup, strtok_r
8+
#include <time.h> // for nanosleep, timespec
9+
10+
#include "capture_filter.h" // for CAPTURE_FILTER_ABI_VERSION, capture_fil...
11+
#include "compat/c23.h" // IWYU pragma: keep
12+
#include "debug.h" // for LOG_LEVEL_ERROR, MSG
13+
#include "lib_common.h" // for REGISTER_MODULE, library_class
14+
#include "tv.h" // for time_ns_t, get_time_in_ns, SEC_TO_NS
15+
#include "types.h" // for video_frame, video_frame_callbacks, tile
16+
#include "utils/color_out.h" // for color_printf, TBOLD
17+
#include "utils/macros.h" // for to_fourcc, IS_KEY_PREFIX, IS_PREFIX
18+
#include "utils/text.h" // for wrap_paragraph
19+
#include "video_frame.h" // for vf_free, vf_alloc_desc, video_desc_from...
20+
struct module;
21+
22+
#define MAGIC to_fourcc('C', 'F', 'a', 'f')
23+
#define MOD_NAME "[cf/add_frame] "
24+
25+
struct state_add_frame {
26+
uint32_t magic;
27+
bool nodelay;
28+
int add_frame_cnt;
29+
int curr_idx;
30+
struct video_frame *cached;
31+
time_ns_t t0;
32+
};
33+
34+
static void
35+
usage()
36+
{
37+
char desc[] = "capture filter " TBOLD("add_frame")
38+
" adds extra frame per every amount of "
39+
"frames.\n\nTypical use case is to convert 50p to 60p.\n";
40+
color_printf("%s\n", wrap_paragraph(desc));
41+
color_printf("Usage:\n"
42+
"\t" TBOLD("-F add_frame:every=<num>[:nodelay]")
43+
"\n");
44+
color_printf(
45+
"\n(in the proposed 50p->60p case, the <num> will be 5)\n\n");
46+
}
47+
48+
static void
49+
done(void *state)
50+
{
51+
struct state_add_frame *s = state;
52+
if (s->cached != nullptr) {
53+
vf_free(s->cached);
54+
}
55+
free(s);
56+
}
57+
58+
static bool
59+
parse_fmt(struct state_add_frame *s, char *fmt)
60+
{
61+
char *tok = nullptr;
62+
char *state = nullptr;
63+
while ((tok = strtok_r(fmt, ":", &state)) != nullptr) {
64+
fmt = nullptr;
65+
if (IS_KEY_PREFIX(tok, "every")) {
66+
s->add_frame_cnt = atoi(strchr(tok, '=') + 1);
67+
} else if (IS_PREFIX(tok, "nodelay")) {
68+
s->nodelay = true;
69+
} else {
70+
MSG(ERROR, "Unknown option: %s!\n", tok);
71+
return false;
72+
}
73+
}
74+
if (s->add_frame_cnt <= 2) {
75+
MSG(ERROR, "Number of frames missing or invalid (%d)!\n",
76+
s->add_frame_cnt);
77+
return false;
78+
}
79+
return true;
80+
}
81+
82+
static int
83+
init(struct module * /* parent */, const char *cfg, void **state)
84+
{
85+
assert(cfg != nullptr);
86+
if (strcmp(cfg, "help") == 0) {
87+
usage();
88+
return 1;
89+
}
90+
struct state_add_frame *s = calloc(1, sizeof *s);
91+
s->magic = MAGIC;
92+
char *ccfg = strdup(cfg);
93+
bool ret = parse_fmt(s, ccfg);
94+
free(ccfg);
95+
if (!ret) {
96+
done(s);
97+
usage();
98+
return -1;
99+
}
100+
*state = s;
101+
return 0;
102+
}
103+
104+
// from ./override_prop.c
105+
static void
106+
dispose_frame(struct video_frame *f)
107+
{
108+
VIDEO_FRAME_DISPOSE((struct video_frame *) f->callbacks.dispose_udata);
109+
vf_free(f);
110+
}
111+
112+
static struct video_frame *
113+
filter(void *state, struct video_frame *in)
114+
{
115+
struct state_add_frame *s = state;
116+
if (in == nullptr) {
117+
if (s->cached == nullptr) {
118+
return nullptr;
119+
}
120+
in = s->cached;
121+
s->cached = nullptr;
122+
}
123+
124+
if (s->curr_idx == 0) {
125+
if (s->cached != nullptr) {
126+
vf_free(s->cached);
127+
}
128+
s->cached = vf_get_copy(in);
129+
s->cached->callbacks.dispose = vf_free;
130+
s->curr_idx += 1;
131+
132+
// wrap with new header (fps adj)
133+
struct video_frame *out =
134+
vf_alloc_desc(video_desc_from_frame(in));
135+
out->fps = out->fps / s->add_frame_cnt * (s->add_frame_cnt + 1);
136+
memcpy(out->tiles, in->tiles,
137+
in->tile_count * sizeof(struct tile));
138+
s->t0 = get_time_in_ns();
139+
return out;
140+
}
141+
142+
// wrap with new header (fps adj)
143+
double new_fps = in->fps / s->add_frame_cnt * (s->add_frame_cnt + 1);
144+
struct video_frame *out = vf_alloc_desc(video_desc_from_frame(in));
145+
out->fps = new_fps;
146+
memcpy(out->tiles, in->tiles, in->tile_count * sizeof(struct tile));
147+
148+
out->callbacks.dispose = dispose_frame;
149+
out->callbacks.dispose_udata = in;
150+
151+
if (!s->nodelay) {
152+
time_ns_t now = get_time_in_ns();
153+
time_ns_t new_frame_budget = SEC_TO_NS(1) / new_fps;
154+
time_ns_t sleep_ns =
155+
(s->t0 + (s->curr_idx * new_frame_budget)) - now;
156+
if (sleep_ns > 0) {
157+
nanosleep(&(struct timespec){ .tv_nsec = sleep_ns },
158+
nullptr);
159+
}
160+
}
161+
162+
s->curr_idx = (s->curr_idx + 1) % (s->add_frame_cnt + 1);
163+
return out;
164+
}
165+
166+
static const struct capture_filter_info capture_filter_blank = {
167+
.init = init,
168+
.done = done,
169+
.filter = filter,
170+
};
171+
172+
REGISTER_MODULE(add_frame, &capture_filter_blank, LIBRARY_CLASS_CAPTURE_FILTER,
173+
CAPTURE_FILTER_ABI_VERSION);

0 commit comments

Comments
 (0)