You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
8.8KB

  1. /*
  2. * Copyright (c) 2017 Paul B Mahol
  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 closed captioning data (EIA-608).
  23. * See also https://en.wikipedia.org/wiki/EIA-608
  24. */
  25. #include <string.h>
  26. #include "libavutil/internal.h"
  27. #include "libavutil/opt.h"
  28. #include "libavutil/pixdesc.h"
  29. #include "libavutil/timestamp.h"
  30. #include "avfilter.h"
  31. #include "formats.h"
  32. #include "internal.h"
  33. #include "video.h"
  34. #define FALL 0
  35. #define RISE 1
  36. typedef struct ReadEIA608Context {
  37. const AVClass *class;
  38. int start, end;
  39. int min_range;
  40. int max_peak_diff;
  41. int max_period_diff;
  42. int max_start_diff;
  43. int nb_found;
  44. int white;
  45. int black;
  46. float mpd, mhd, msd, mac, spw, bhd, wth, bth;
  47. int chp;
  48. } ReadEIA608Context;
  49. #define OFFSET(x) offsetof(ReadEIA608Context, x)
  50. #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
  51. static const AVOption readeia608_options[] = {
  52. { "scan_min", "set from which line to scan for codes", OFFSET(start), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
  53. { "scan_max", "set to which line to scan for codes", OFFSET(end), AV_OPT_TYPE_INT, {.i64=29}, 0, INT_MAX, FLAGS },
  54. { "mac", "set minimal acceptable amplitude change for sync codes detection", OFFSET(mac), AV_OPT_TYPE_FLOAT, {.dbl=.2}, 0.001, 1, FLAGS },
  55. { "spw", "set ratio of width reserved for sync code detection", OFFSET(spw), AV_OPT_TYPE_FLOAT, {.dbl=.27}, 0.1, 0.7, FLAGS },
  56. { "mhd", "set max peaks height difference for sync code detection", OFFSET(mhd), AV_OPT_TYPE_FLOAT, {.dbl=.1}, 0, 0.5, FLAGS },
  57. { "mpd", "set max peaks period difference for sync code detection", OFFSET(mpd), AV_OPT_TYPE_FLOAT, {.dbl=.1}, 0, 0.5, FLAGS },
  58. { "msd", "set first two max start code bits differences", OFFSET(msd), AV_OPT_TYPE_FLOAT, {.dbl=.02}, 0, 0.5, FLAGS },
  59. { "bhd", "set min ratio of bits height compared to 3rd start code bit", OFFSET(bhd), AV_OPT_TYPE_FLOAT, {.dbl=.75}, 0.01, 1, FLAGS },
  60. { "th_w", "set white color threshold", OFFSET(wth), AV_OPT_TYPE_FLOAT, {.dbl=.35}, 0.1, 1, FLAGS },
  61. { "th_b", "set black color threshold", OFFSET(bth), AV_OPT_TYPE_FLOAT, {.dbl=.15}, 0, 0.5, FLAGS },
  62. { "chp", "check and apply parity bit", OFFSET(chp), AV_OPT_TYPE_BOOL, {.i64= 0}, 0, 1, FLAGS },
  63. { NULL }
  64. };
  65. AVFILTER_DEFINE_CLASS(readeia608);
  66. static int query_formats(AVFilterContext *ctx)
  67. {
  68. static const enum AVPixelFormat pixel_fmts[] = {
  69. AV_PIX_FMT_GRAY8,
  70. AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P,
  71. AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P,
  72. AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P,
  73. AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P,
  74. AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ444P,
  75. AV_PIX_FMT_YUVJ411P,
  76. AV_PIX_FMT_NONE
  77. };
  78. AVFilterFormats *formats = ff_make_format_list(pixel_fmts);
  79. if (!formats)
  80. return AVERROR(ENOMEM);
  81. return ff_set_common_formats(ctx, formats);
  82. }
  83. static int config_input(AVFilterLink *inlink)
  84. {
  85. const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
  86. AVFilterContext *ctx = inlink->dst;
  87. ReadEIA608Context *s = ctx->priv;
  88. int depth = desc->comp[0].depth;
  89. if (s->end >= inlink->h) {
  90. av_log(ctx, AV_LOG_WARNING, "Last line to scan too large, clipping.\n");
  91. s->end = inlink->h - 1;
  92. }
  93. if (s->start > s->end) {
  94. av_log(ctx, AV_LOG_ERROR, "Invalid range.\n");
  95. return AVERROR(EINVAL);
  96. }
  97. s->min_range = s->mac * ((1 << depth) - 1);
  98. s->max_peak_diff = s->mhd * ((1 << depth) - 1);
  99. s->max_period_diff = s->mpd * ((1 << depth) - 1);
  100. s->max_start_diff = s->msd * ((1 << depth) - 1);
  101. s->white = s->wth * ((1 << depth) - 1);
  102. s->black = s->bth * ((1 << depth) - 1);
  103. return 0;
  104. }
  105. static void extract_line(AVFilterContext *ctx, AVFilterLink *inlink, AVFrame *in, int line)
  106. {
  107. ReadEIA608Context *s = ctx->priv;
  108. int max = 0, min = INT_MAX;
  109. int i, ch, range = 0;
  110. const uint8_t *src;
  111. uint16_t clock[8][2] = { { 0 } };
  112. const int sync_width = s->spw * in->width;
  113. int last = 0, peaks = 0, max_peak_diff = 0, dir = RISE;
  114. const int width_per_bit = (in->width - sync_width) / 19;
  115. uint8_t byte[2] = { 0 };
  116. int s1, s2, s3, parity;
  117. src = &in->data[0][line * in->linesize[0]];
  118. for (i = 0; i < sync_width; i++) {
  119. max = FFMAX(max, src[i]);
  120. min = FFMIN(min, src[i]);
  121. }
  122. range = max - min;
  123. if (range < s->min_range)
  124. return;
  125. for (i = 0; i < sync_width; i++) {
  126. int Y = src[i];
  127. if (dir == RISE) {
  128. if (Y < last) {
  129. dir = FALL;
  130. if (last >= s->white) {
  131. clock[peaks][0] = last;
  132. clock[peaks][1] = i;
  133. peaks++;
  134. if (peaks > 7)
  135. break;
  136. }
  137. }
  138. } else if (dir == FALL) {
  139. if (Y > last && last <= s->black) {
  140. dir = RISE;
  141. }
  142. }
  143. last = Y;
  144. }
  145. if (peaks != 7)
  146. return;
  147. for (i = 1; i < 7; i++)
  148. max_peak_diff = FFMAX(max_peak_diff, FFABS(clock[i][0] - clock[i-1][0]));
  149. if (max_peak_diff > s->max_peak_diff)
  150. return;
  151. max = 0; min = INT_MAX;
  152. for (i = 1; i < 7; i++) {
  153. max = FFMAX(max, FFABS(clock[i][1] - clock[i-1][1]));
  154. min = FFMIN(min, FFABS(clock[i][1] - clock[i-1][1]));
  155. }
  156. range = max - min;
  157. if (range > s->max_period_diff)
  158. return;
  159. s1 = src[sync_width + width_per_bit * 0 + width_per_bit / 2];
  160. s2 = src[sync_width + width_per_bit * 1 + width_per_bit / 2];
  161. s3 = src[sync_width + width_per_bit * 2 + width_per_bit / 2];
  162. if (FFABS(s1 - s2) > s->max_start_diff || s1 > s->black || s2 > s->black || s3 < s->white)
  163. return;
  164. for (ch = 0; ch < 2; ch++) {
  165. for (parity = 0, i = 0; i < 8; i++) {
  166. int b = src[sync_width + width_per_bit * (i + 3 + 8 * ch) + width_per_bit / 2];
  167. if (b - s1 > (s3 - s1) * s->bhd) {
  168. b = 1;
  169. parity++;
  170. } else {
  171. b = 0;
  172. }
  173. byte[ch] |= b << i;
  174. }
  175. if (s->chp) {
  176. if (!(parity & 1)) {
  177. byte[ch] = 0;
  178. }
  179. }
  180. }
  181. {
  182. uint8_t key[128], value[128];
  183. snprintf(key, sizeof(key), "lavfi.readeia608.%d.cc", s->nb_found);
  184. snprintf(value, sizeof(value), "0x%02X%02X", byte[0], byte[1]);
  185. av_dict_set(&in->metadata, key, value, 0);
  186. snprintf(key, sizeof(key), "lavfi.readeia608.%d.line", s->nb_found);
  187. snprintf(value, sizeof(value), "%d", line);
  188. av_dict_set(&in->metadata, key, value, 0);
  189. }
  190. s->nb_found++;
  191. }
  192. static int filter_frame(AVFilterLink *inlink, AVFrame *in)
  193. {
  194. AVFilterContext *ctx = inlink->dst;
  195. AVFilterLink *outlink = ctx->outputs[0];
  196. ReadEIA608Context *s = ctx->priv;
  197. int i;
  198. s->nb_found = 0;
  199. for (i = s->start; i <= s->end; i++)
  200. extract_line(ctx, inlink, in, i);
  201. return ff_filter_frame(outlink, in);
  202. }
  203. static const AVFilterPad readeia608_inputs[] = {
  204. {
  205. .name = "default",
  206. .type = AVMEDIA_TYPE_VIDEO,
  207. .filter_frame = filter_frame,
  208. .config_props = config_input,
  209. },
  210. { NULL }
  211. };
  212. static const AVFilterPad readeia608_outputs[] = {
  213. {
  214. .name = "default",
  215. .type = AVMEDIA_TYPE_VIDEO,
  216. },
  217. { NULL }
  218. };
  219. AVFilter ff_vf_readeia608 = {
  220. .name = "readeia608",
  221. .description = NULL_IF_CONFIG_SMALL("Read EIA-608 Closed Caption codes from input video and write them to frame metadata."),
  222. .priv_size = sizeof(ReadEIA608Context),
  223. .priv_class = &readeia608_class,
  224. .query_formats = query_formats,
  225. .inputs = readeia608_inputs,
  226. .outputs = readeia608_outputs,
  227. .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
  228. };