blob: d70af6a9ee1b711b3c232a4562d936f03357179a
1 | /* |
2 | * Copyright (c) 2016 Tobias Rapp |
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 reading the vertical interval timecode (VITC). |
24 | * See also https://en.wikipedia.org/wiki/Vertical_interval_timecode |
25 | */ |
26 | |
27 | #include "libavutil/common.h" |
28 | #include "libavutil/internal.h" |
29 | #include "libavutil/opt.h" |
30 | #include "libavutil/pixdesc.h" |
31 | #include "libavutil/timecode.h" |
32 | #include "avfilter.h" |
33 | #include "formats.h" |
34 | #include "internal.h" |
35 | |
36 | #define LINE_DATA_SIZE 9 |
37 | |
38 | typedef struct ReadVitcContext { |
39 | const AVClass *class; |
40 | |
41 | int scan_max; |
42 | double thr_b; |
43 | double thr_w; |
44 | |
45 | int threshold_black; |
46 | int threshold_white; |
47 | int threshold_gray; |
48 | int grp_width; |
49 | uint8_t line_data[LINE_DATA_SIZE]; |
50 | char tcbuf[AV_TIMECODE_STR_SIZE]; |
51 | } ReadVitcContext; |
52 | |
53 | #define OFFSET(x) offsetof(ReadVitcContext, x) |
54 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM |
55 | |
56 | static const AVOption readvitc_options[] = { |
57 | { "scan_max", "maximum line numbers to scan for VITC data", OFFSET(scan_max), AV_OPT_TYPE_INT, {.i64 = 45 }, -1, INT_MAX, FLAGS }, |
58 | { "thr_b", "black color threshold", OFFSET(thr_b), AV_OPT_TYPE_DOUBLE, {.dbl = 0.2 }, 0, 1.0, FLAGS }, |
59 | { "thr_w", "white color threshold", OFFSET(thr_w), AV_OPT_TYPE_DOUBLE, {.dbl = 0.6 }, 0, 1.0, FLAGS }, |
60 | { NULL } |
61 | }; |
62 | |
63 | AVFILTER_DEFINE_CLASS(readvitc); |
64 | |
65 | static uint8_t get_vitc_crc( uint8_t *line ) { |
66 | uint8_t crc; |
67 | |
68 | crc = 0x01 | (line[0] << 2); |
69 | crc ^= (line[0] >> 6) | 0x04 | (line[1] << 4); |
70 | crc ^= (line[1] >> 4) | 0x10 | (line[2] << 6); |
71 | crc ^= (line[2] >> 2) | 0x40; |
72 | crc ^= line[3]; |
73 | crc ^= 0x01 | (line[4] << 2); |
74 | crc ^= (line[4] >> 6) | 0x04 | (line[5] << 4); |
75 | crc ^= (line[5] >> 4) | 0x10 | (line[6] << 6); |
76 | crc ^= (line[6] >> 2) | 0x40; |
77 | crc ^= line[7]; |
78 | crc ^= 0x01; |
79 | crc = (crc >> 2) | (crc << 6); // rotate byte right by two bits |
80 | return crc; |
81 | } |
82 | |
83 | static inline uint8_t get_pit_avg3( uint8_t *line, int i ) { |
84 | return ((line[i-1] + line[i] + line[i+1]) / 3); |
85 | } |
86 | |
87 | static int read_vitc_line( ReadVitcContext *ctx, uint8_t *src, int line_size, int width, int height ) |
88 | { |
89 | uint8_t *scan_line; |
90 | int grp_index, pit_index; |
91 | int grp_start_pos; |
92 | uint8_t pit_value; |
93 | int x, y, res = 0; |
94 | |
95 | if (ctx->scan_max >= 0) |
96 | height = FFMIN(height, ctx->scan_max); |
97 | |
98 | // scan lines for VITC data, starting from the top |
99 | for (y = 0; y < height; y++) { |
100 | scan_line = src; |
101 | memset(ctx->line_data, 0, LINE_DATA_SIZE); |
102 | grp_index = 0; |
103 | x = 0; |
104 | while ((x < width) && (grp_index < 9)) { |
105 | // search next sync pattern |
106 | while ((x < width) && (scan_line[x] < ctx->threshold_white)) |
107 | x++; |
108 | while ((x < width) && (scan_line[x] > ctx->threshold_black)) |
109 | x++; |
110 | x = FFMAX(x - ((ctx->grp_width+10) / 20), 1); // step back a half pit |
111 | grp_start_pos = x; |
112 | if ((grp_start_pos + ctx->grp_width) > width) |
113 | break; // not enough pixels for reading a whole pit group |
114 | pit_value = get_pit_avg3(scan_line, x); |
115 | if (pit_value < ctx->threshold_white) |
116 | break; // first sync bit mismatch |
117 | x = grp_start_pos + ((ctx->grp_width) / 10); |
118 | pit_value = get_pit_avg3(scan_line, x); |
119 | if (pit_value > ctx->threshold_black ) |
120 | break; // second sync bit mismatch |
121 | for (pit_index = 0; pit_index <= 7; pit_index++) { |
122 | x = grp_start_pos + (((pit_index+2)*ctx->grp_width) / 10); |
123 | pit_value = get_pit_avg3(scan_line, x); |
124 | if (pit_value > ctx->threshold_gray) |
125 | ctx->line_data[grp_index] |= (1 << pit_index); |
126 | } |
127 | grp_index++; |
128 | } |
129 | if ((grp_index == 9) && (get_vitc_crc(ctx->line_data) == ctx->line_data[8])) { |
130 | res = 1; |
131 | break; |
132 | } |
133 | src += line_size; |
134 | } |
135 | |
136 | return res; |
137 | } |
138 | |
139 | static unsigned bcd2uint(uint8_t high, uint8_t low) |
140 | { |
141 | if (high > 9 || low > 9) |
142 | return 0; |
143 | return 10*high + low; |
144 | } |
145 | |
146 | static char *make_vitc_tc_string(char *buf, uint8_t *line) |
147 | { |
148 | unsigned hh = bcd2uint(line[7] & 0x03, line[6] & 0x0f); // 6-bit hours |
149 | unsigned mm = bcd2uint(line[5] & 0x07, line[4] & 0x0f); // 7-bit minutes |
150 | unsigned ss = bcd2uint(line[3] & 0x07, line[2] & 0x0f); // 7-bit seconds |
151 | unsigned ff = bcd2uint(line[1] & 0x03, line[0] & 0x0f); // 6-bit frames |
152 | unsigned drop = (line[1] & 0x04); // 1-bit drop flag |
153 | snprintf(buf, AV_TIMECODE_STR_SIZE, "%02u:%02u:%02u%c%02u", |
154 | hh, mm, ss, drop ? ';' : ':', ff); |
155 | return buf; |
156 | } |
157 | |
158 | static av_cold int init(AVFilterContext *ctx) |
159 | { |
160 | ReadVitcContext *s = ctx->priv; |
161 | |
162 | s->threshold_black = s->thr_b * UINT8_MAX; |
163 | s->threshold_white = s->thr_w * UINT8_MAX; |
164 | if (s->threshold_black > s->threshold_white) { |
165 | av_log(ctx, AV_LOG_WARNING, "Black color threshold is higher than white color threshold (%g > %g)\n", |
166 | s->thr_b, s->thr_w); |
167 | return AVERROR(EINVAL); |
168 | } |
169 | s->threshold_gray = s->threshold_white - ((s->threshold_white - s->threshold_black) / 2); |
170 | av_log(ctx, AV_LOG_DEBUG, "threshold_black:%d threshold_white:%d threshold_gray:%d\n", |
171 | s->threshold_black, s->threshold_white, s->threshold_gray); |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static int config_props(AVFilterLink *inlink) |
177 | { |
178 | AVFilterContext *ctx = inlink->dst; |
179 | ReadVitcContext *s = ctx->priv; |
180 | |
181 | s->grp_width = inlink->w * 5 / 48; |
182 | av_log(ctx, AV_LOG_DEBUG, "w:%d h:%d grp_width:%d scan_max:%d\n", |
183 | inlink->w, inlink->h, s->grp_width, s->scan_max); |
184 | return 0; |
185 | } |
186 | |
187 | static int query_formats(AVFilterContext *ctx) |
188 | { |
189 | static const enum AVPixelFormat pixel_fmts[] = { |
190 | AV_PIX_FMT_GRAY8, |
191 | AV_PIX_FMT_NV12, |
192 | AV_PIX_FMT_NV16, |
193 | AV_PIX_FMT_NV21, |
194 | AV_PIX_FMT_YUV410P, |
195 | AV_PIX_FMT_YUV411P, |
196 | AV_PIX_FMT_YUV420P, |
197 | AV_PIX_FMT_YUV422P, |
198 | AV_PIX_FMT_YUV440P, |
199 | AV_PIX_FMT_YUV444P, |
200 | AV_PIX_FMT_YUVA420P, |
201 | AV_PIX_FMT_YUVA422P, |
202 | AV_PIX_FMT_YUVA444P, |
203 | AV_PIX_FMT_YUVJ411P, |
204 | AV_PIX_FMT_YUVJ420P, |
205 | AV_PIX_FMT_YUVJ422P, |
206 | AV_PIX_FMT_YUVJ440P, |
207 | AV_PIX_FMT_YUVJ444P, |
208 | AV_PIX_FMT_NONE |
209 | }; |
210 | AVFilterFormats *fmts_list = ff_make_format_list(pixel_fmts); |
211 | if (!fmts_list) |
212 | return AVERROR(ENOMEM); |
213 | return ff_set_common_formats(ctx, fmts_list); |
214 | } |
215 | |
216 | static int filter_frame(AVFilterLink *inlink, AVFrame *frame) |
217 | { |
218 | AVFilterContext *ctx = inlink->dst; |
219 | AVFilterLink *outlink = ctx->outputs[0]; |
220 | ReadVitcContext *s = ctx->priv; |
221 | int found; |
222 | |
223 | found = read_vitc_line(s, frame->data[0], frame->linesize[0], inlink->w, inlink->h); |
224 | av_dict_set(avpriv_frame_get_metadatap(frame), "lavfi.readvitc.found", (found ? "1" : "0"), 0); |
225 | if (found) |
226 | av_dict_set(avpriv_frame_get_metadatap(frame), "lavfi.readvitc.tc_str", make_vitc_tc_string(s->tcbuf, s->line_data), 0); |
227 | |
228 | return ff_filter_frame(outlink, frame); |
229 | } |
230 | |
231 | static const AVFilterPad inputs[] = { |
232 | { |
233 | .name = "default", |
234 | .type = AVMEDIA_TYPE_VIDEO, |
235 | .filter_frame = filter_frame, |
236 | .config_props = config_props, |
237 | }, |
238 | { NULL } |
239 | }; |
240 | |
241 | static const AVFilterPad outputs[] = { |
242 | { |
243 | .name = "default", |
244 | .type = AVMEDIA_TYPE_VIDEO, |
245 | }, |
246 | { NULL } |
247 | }; |
248 | |
249 | AVFilter ff_vf_readvitc = { |
250 | .name = "readvitc", |
251 | .description = NULL_IF_CONFIG_SMALL("Read vertical interval timecode and write it to frame metadata."), |
252 | .priv_size = sizeof(ReadVitcContext), |
253 | .priv_class = &readvitc_class, |
254 | .inputs = inputs, |
255 | .outputs = outputs, |
256 | .init = init, |
257 | .query_formats = query_formats, |
258 | }; |
259 |