FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_fsync.c
Date: 2024-05-04 02:01:39
Exec Total Coverage
Lines: 99 116 85.3%
Functions: 9 9 100.0%
Branches: 43 60 71.7%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2023 Thilo Borgmann <thilo.borgmann _at_ mail.de>
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /**
22 * @file
23 * Filter for syncing video frames from external source
24 *
25 * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
26 */
27
28 #include "libavutil/avstring.h"
29 #include "libavutil/error.h"
30 #include "libavutil/mem.h"
31 #include "libavutil/opt.h"
32 #include "libavformat/avio.h"
33 #include "video.h"
34 #include "filters.h"
35
36 #define BUF_SIZE 256
37
38 typedef struct FsyncContext {
39 const AVClass *class;
40 AVIOContext *avio_ctx; // reading the map file
41 AVFrame *last_frame; // buffering the last frame for duplicating eventually
42 char *filename; // user-specified map file
43 char *buf; // line buffer for the map file
44 char *cur; // current position in the line buffer
45 char *end; // end pointer of the line buffer
46 int64_t ptsi; // input pts to map to [0-N] output pts
47 int64_t pts; // output pts
48 int tb_num; // output timebase num
49 int tb_den; // output timebase den
50 } FsyncContext;
51
52 #define OFFSET(x) offsetof(FsyncContext, x)
53
54 static const AVOption fsync_options[] = {
55 { "file", "set the file name to use for frame sync", OFFSET(filename), AV_OPT_TYPE_STRING, { .str = "" }, .flags= AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM },
56 { "f", "set the file name to use for frame sync", OFFSET(filename), AV_OPT_TYPE_STRING, { .str = "" }, .flags= AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM },
57 { NULL }
58 };
59
60 /**
61 * Fills the buffer from cur to end, add \0 at EOF
62 */
63 6 static int buf_fill(FsyncContext *ctx)
64 {
65 int ret;
66 6 int num = ctx->end - ctx->cur;
67
68 6 ret = avio_read(ctx->avio_ctx, ctx->cur, num);
69
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (ret < 0)
70 return ret;
71
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (ret < num) {
72 3 *(ctx->cur + ret) = '\0';
73 }
74
75 6 return ret;
76 }
77
78 /**
79 * Copies cur to end to the beginning and fills the rest
80 */
81 2 static int buf_reload(FsyncContext *ctx)
82 {
83 int i, ret;
84 2 int num = ctx->end - ctx->cur;
85
86
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (i = 0; i < num; i++) {
87 6 ctx->buf[i] = *ctx->cur++;
88 }
89
90 2 ctx->cur = ctx->buf + i;
91 2 ret = buf_fill(ctx);
92
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
93 return ret;
94 2 ctx->cur = ctx->buf;
95
96 2 return ret;
97 }
98
99 /**
100 * Skip from cur over eol
101 */
102 165 static void buf_skip_eol(FsyncContext *ctx)
103 {
104 char *i;
105
1/2
✓ Branch 0 taken 229 times.
✗ Branch 1 not taken.
229 for (i = ctx->cur; i < ctx->end; i++) {
106
2/2
✓ Branch 0 taken 165 times.
✓ Branch 1 taken 64 times.
229 if (*i != '\n')// && *i != '\r')
107 165 break;
108 }
109 165 ctx->cur = i;
110 165 }
111
112 /**
113 * Get number of bytes from cur until eol
114 *
115 * @return >= 0 in case of success,
116 * -1 in case there is no line ending before end of buffer
117 */
118 167 static int buf_get_line_count(FsyncContext *ctx)
119 {
120 167 int ret = 0;
121 char *i;
122
2/2
✓ Branch 0 taken 1597 times.
✓ Branch 1 taken 2 times.
1599 for (i = ctx->cur; i < ctx->end; i++, ret++) {
123
4/4
✓ Branch 0 taken 1594 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 162 times.
✓ Branch 3 taken 1432 times.
1597 if (*i == '\0' || *i == '\n')
124 165 return ret;
125 }
126
127 2 return -1;
128 }
129
130 /**
131 * Get number of bytes from cur to '\0'
132 */
133 75 static int buf_get_zero(FsyncContext *ctx)
134 {
135 75 return av_strnlen(ctx->cur, ctx->end - ctx->cur);
136 }
137
138 165 static int activate(AVFilterContext *ctx)
139 {
140 165 FsyncContext *s = ctx->priv;
141 165 AVFilterLink *inlink = ctx->inputs[0];
142 165 AVFilterLink *outlink = ctx->outputs[0];
143
144 int ret, line_count;
145 AVFrame *frame;
146
147
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 165 times.
165 FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
148
149 165 buf_skip_eol(s);
150 165 line_count = buf_get_line_count(s);
151
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 163 times.
165 if (line_count < 0) {
152 2 line_count = buf_reload(s);
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (line_count < 0)
154 return line_count;
155 2 line_count = buf_get_line_count(s);
156
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (line_count < 0)
157 return line_count;
158 }
159
160
4/4
✓ Branch 1 taken 72 times.
✓ Branch 2 taken 93 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 69 times.
165 if (avio_feof(s->avio_ctx) && buf_get_zero(s) < 3) {
161 3 av_log(ctx, AV_LOG_DEBUG, "End of file. To zero = %i\n", buf_get_zero(s));
162 3 goto end;
163 }
164
165
2/2
✓ Branch 0 taken 111 times.
✓ Branch 1 taken 51 times.
162 if (s->last_frame) {
166 111 ret = av_sscanf(s->cur, "%"PRId64" %"PRId64" %d/%d", &s->ptsi, &s->pts, &s->tb_num, &s->tb_den);
167
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 111 times.
111 if (ret != 4) {
168 av_log(ctx, AV_LOG_ERROR, "Unexpected format found (%i / 4).\n", ret);
169 ff_outlink_set_status(outlink, AVERROR_INVALIDDATA, AV_NOPTS_VALUE);
170 return AVERROR_INVALIDDATA;
171 }
172
173 111 av_log(ctx, AV_LOG_DEBUG, "frame %"PRId64" ", s->last_frame->pts);
174
175
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 47 times.
111 if (s->last_frame->pts >= s->ptsi) {
176 64 av_log(ctx, AV_LOG_DEBUG, ">= %"PRId64": DUP LAST with pts = %"PRId64"\n", s->ptsi, s->pts);
177
178 // clone frame
179 64 frame = av_frame_clone(s->last_frame);
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 64 times.
64 if (!frame) {
181 ff_outlink_set_status(outlink, AVERROR(ENOMEM), AV_NOPTS_VALUE);
182 return AVERROR(ENOMEM);
183 }
184
185 // set output pts and timebase
186 64 frame->pts = s->pts;
187 64 frame->time_base = av_make_q((int)s->tb_num, (int)s->tb_den);
188
189 // advance cur to eol, skip over eol in the next call
190 64 s->cur += line_count;
191
192 // call again
193
1/2
✓ Branch 1 taken 64 times.
✗ Branch 2 not taken.
64 if (ff_inoutlink_check_flow(inlink, outlink))
194 64 ff_filter_set_ready(ctx, 100);
195
196 // filter frame
197 64 return ff_filter_frame(outlink, frame);
198
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 47 times.
47 } else if (s->last_frame->pts < s->ptsi) {
199 47 av_log(ctx, AV_LOG_DEBUG, "< %"PRId64": DROP\n", s->ptsi);
200 47 av_frame_free(&s->last_frame);
201
202 // call again
203
1/2
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
47 if (ff_inoutlink_check_flow(inlink, outlink))
204 47 ff_filter_set_ready(ctx, 100);
205
206 47 return 0;
207 }
208 }
209
210 51 end:
211
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 51 times.
54 if (s->last_frame)
212 3 av_frame_free(&s->last_frame);
213
214 54 ret = ff_inlink_consume_frame(inlink, &s->last_frame);
215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
54 if (ret < 0)
216 return ret;
217
218
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 52 times.
54 FF_FILTER_FORWARD_STATUS(inlink, outlink);
219
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
52 FF_FILTER_FORWARD_WANTED(outlink, inlink);
220
221 return FFERROR_NOT_READY;
222 }
223
224 2 static int fsync_config_props(AVFilterLink* outlink)
225 {
226 2 AVFilterContext *ctx = outlink->src;
227 2 FsyncContext *s = ctx->priv;
228 int ret;
229
230 // read first line to get output timebase
231 2 ret = av_sscanf(s->cur, "%"PRId64" %"PRId64" %d/%d", &s->ptsi, &s->pts, &s->tb_num, &s->tb_den);
232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret != 4) {
233 av_log(ctx, AV_LOG_ERROR, "Unexpected format found (%i of 4).\n", ret);
234 ff_outlink_set_status(outlink, AVERROR_INVALIDDATA, AV_NOPTS_VALUE);
235 return AVERROR_INVALIDDATA;
236 }
237
238 2 outlink->frame_rate = av_make_q(1, 0); // unknown or dynamic
239 2 outlink->time_base = av_make_q(s->tb_num, s->tb_den);
240
241 2 return 0;
242 }
243
244 4 static av_cold int fsync_init(AVFilterContext *ctx)
245 {
246 4 FsyncContext *s = ctx->priv;
247 int ret;
248
249 4 av_log(ctx, AV_LOG_DEBUG, "filename: %s\n", s->filename);
250
251 4 s->buf = av_malloc(BUF_SIZE + 1);
252
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (!s->buf)
253 return AVERROR(ENOMEM);
254
255 4 ret = avio_open(&s->avio_ctx, s->filename, AVIO_FLAG_READ);
256
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
257 return ret;
258
259 4 s->cur = s->buf;
260 4 s->end = s->buf + BUF_SIZE;
261 4 s->buf[BUF_SIZE] = '\0';
262
263 4 ret = buf_fill(s);
264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
265 return ret;
266
267
268 4 return 0;
269 }
270
271 4 static av_cold void fsync_uninit(AVFilterContext *ctx)
272 {
273 4 FsyncContext *s = ctx->priv;
274
275 4 avio_closep(&s->avio_ctx);
276 4 av_freep(&s->buf);
277 4 av_frame_free(&s->last_frame);
278 4 }
279
280 AVFILTER_DEFINE_CLASS(fsync);
281
282 static const AVFilterPad fsync_outputs[] = {
283 {
284 .name = "default",
285 .type = AVMEDIA_TYPE_VIDEO,
286 .config_props = fsync_config_props,
287 },
288 };
289
290 const AVFilter ff_vf_fsync = {
291 .name = "fsync",
292 .description = NULL_IF_CONFIG_SMALL("Synchronize video frames from external source."),
293 .init = fsync_init,
294 .uninit = fsync_uninit,
295 .priv_size = sizeof(FsyncContext),
296 .priv_class = &fsync_class,
297 .activate = activate,
298 .formats_state = FF_FILTER_FORMATS_PASSTHROUGH,
299 FILTER_INPUTS(ff_video_default_filterpad),
300 FILTER_OUTPUTS(fsync_outputs),
301 .flags = AVFILTER_FLAG_METADATA_ONLY,
302 };
303