vf_readvitc.c 8.3 KB

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