1 /*
2 * Copyright (c) 2017 Gerion Entrup
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (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
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 /**
22 * @file
23 * MPEG-7 video signature calculation and lookup filter
24 * @see http://epubs.surrey.ac.uk/531590/1/MPEG-7%20Video%20Signature%20Author%27s%20Copy.pdf
25 */
26
27 #include <float.h>
28 #include "libavcodec/put_bits.h"
29 #include "libavformat/avformat.h"
30 #include "libavutil/opt.h"
31 #include "libavutil/avstring.h"
32 #include "libavutil/intreadwrite.h"
33 #include "libavutil/timestamp.h"
34 #include "avfilter.h"
35 #include "internal.h"
36 #include "signature.h"
37 #include "signature_lookup.c"
38
39 #define OFFSET(x) offsetof(SignatureContext, x)
40 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM
41 #define BLOCK_LCM (int64_t) 476985600
42
43 static const AVOption signature_options[] = {
44 { "detectmode", "set the detectmode",
45 OFFSET(mode), AV_OPT_TYPE_INT, {.i64 = MODE_OFF}, 0, NB_LOOKUP_MODE-1, FLAGS, "mode" },
46 { "off", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_OFF}, 0, 0, .flags = FLAGS, "mode" },
47 { "full", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_FULL}, 0, 0, .flags = FLAGS, "mode" },
48 { "fast", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = MODE_FAST}, 0, 0, .flags = FLAGS, "mode" },
49 { "nb_inputs", "number of inputs",
50 OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, FLAGS },
51 { "filename", "filename for output files",
52 OFFSET(filename), AV_OPT_TYPE_STRING, {.str = ""}, 0, NB_FORMATS-1, FLAGS },
53 { "format", "set output format",
54 OFFSET(format), AV_OPT_TYPE_INT, {.i64 = FORMAT_BINARY}, 0, 1, FLAGS , "format" },
55 { "binary", 0, 0, AV_OPT_TYPE_CONST, {.i64=FORMAT_BINARY}, 0, 0, FLAGS, "format" },
56 { "xml", 0, 0, AV_OPT_TYPE_CONST, {.i64=FORMAT_XML}, 0, 0, FLAGS, "format" },
57 { "th_d", "threshold to detect one word as similar",
58 OFFSET(thworddist), AV_OPT_TYPE_INT, {.i64 = 9000}, 1, INT_MAX, FLAGS },
59 { "th_dc", "threshold to detect all words as similar",
60 OFFSET(thcomposdist), AV_OPT_TYPE_INT, {.i64 = 60000}, 1, INT_MAX, FLAGS },
61 { "th_xh", "threshold to detect frames as similar",
62 OFFSET(thl1), AV_OPT_TYPE_INT, {.i64 = 116}, 1, INT_MAX, FLAGS },
63 { "th_di", "minimum length of matching sequence in frames",
64 OFFSET(thdi), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, FLAGS },
65 { "th_it", "threshold for relation of good to all frames",
66 OFFSET(thit), AV_OPT_TYPE_DOUBLE, {.dbl = 0.5}, 0.0, 1.0, FLAGS },
67 { NULL }
68 };
69
70 AVFILTER_DEFINE_CLASS(signature);
71
72 /* all formats with a separate gray value */
73 static const enum AVPixelFormat pix_fmts[] = {
74 AV_PIX_FMT_GRAY8,
75 AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P,
76 AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P,
77 AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P,
78 AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ420P,
79 AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P,
80 AV_PIX_FMT_YUVJ440P,
81 AV_PIX_FMT_NV12, AV_PIX_FMT_NV21,
82 AV_PIX_FMT_NONE
83 };
84
config_input(AVFilterLink * inlink)85 static int config_input(AVFilterLink *inlink)
86 {
87 AVFilterContext *ctx = inlink->dst;
88 SignatureContext *sic = ctx->priv;
89 StreamContext *sc = &(sic->streamcontexts[FF_INLINK_IDX(inlink)]);
90
91 sc->time_base = inlink->time_base;
92 /* test for overflow */
93 sc->divide = (((uint64_t) inlink->w/32) * (inlink->w/32 + 1) * (inlink->h/32 * inlink->h/32 + 1) > INT64_MAX / (BLOCK_LCM * 255));
94 if (sc->divide) {
95 av_log(ctx, AV_LOG_WARNING, "Input dimension too high for precise calculation, numbers will be rounded.\n");
96 }
97 sc->w = inlink->w;
98 sc->h = inlink->h;
99 return 0;
100 }
101
get_block_size(const Block * b)102 static int get_block_size(const Block *b)
103 {
104 return (b->to.y - b->up.y + 1) * (b->to.x - b->up.x + 1);
105 }
106
get_block_sum(StreamContext * sc,uint64_t intpic[32][32],const Block * b)107 static uint64_t get_block_sum(StreamContext *sc, uint64_t intpic[32][32], const Block *b)
108 {
109 uint64_t sum = 0;
110
111 int x0, y0, x1, y1;
112
113 x0 = b->up.x;
114 y0 = b->up.y;
115 x1 = b->to.x;
116 y1 = b->to.y;
117
118 if (x0-1 >= 0 && y0-1 >= 0) {
119 sum = intpic[y1][x1] + intpic[y0-1][x0-1] - intpic[y1][x0-1] - intpic[y0-1][x1];
120 } else if (x0-1 >= 0) {
121 sum = intpic[y1][x1] - intpic[y1][x0-1];
122 } else if (y0-1 >= 0) {
123 sum = intpic[y1][x1] - intpic[y0-1][x1];
124 } else {
125 sum = intpic[y1][x1];
126 }
127 return sum;
128 }
129
cmp(const void * x,const void * y)130 static int cmp(const void *x, const void *y)
131 {
132 const uint64_t *a = x, *b = y;
133 return *a < *b ? -1 : ( *a > *b ? 1 : 0 );
134 }
135
136 /**
137 * sets the bit at position pos to 1 in data
138 */
set_bit(uint8_t * data,size_t pos)139 static void set_bit(uint8_t* data, size_t pos)
140 {
141 uint8_t mask = 1 << 7-(pos%8);
142 data[pos/8] |= mask;
143 }
144
filter_frame(AVFilterLink * inlink,AVFrame * picref)145 static int filter_frame(AVFilterLink *inlink, AVFrame *picref)
146 {
147 AVFilterContext *ctx = inlink->dst;
148 SignatureContext *sic = ctx->priv;
149 StreamContext *sc = &(sic->streamcontexts[FF_INLINK_IDX(inlink)]);
150 FineSignature* fs;
151
152 static const uint8_t pot3[5] = { 3*3*3*3, 3*3*3, 3*3, 3, 1 };
153 /* indexes of words : 210,217,219,274,334 44,175,233,270,273 57,70,103,237,269 100,285,295,337,354 101,102,111,275,296
154 s2usw = sorted to unsorted wordvec: 44 is at index 5, 57 at index 10...
155 */
156 static const unsigned int wordvec[25] = {44,57,70,100,101,102,103,111,175,210,217,219,233,237,269,270,273,274,275,285,295,296,334,337,354};
157 static const uint8_t s2usw[25] = { 5,10,11, 15, 20, 21, 12, 22, 6, 0, 1, 2, 7, 13, 14, 8, 9, 3, 23, 16, 17, 24, 4, 18, 19};
158
159 uint8_t wordt2b[5] = { 0, 0, 0, 0, 0 }; /* word ternary to binary */
160 uint64_t intpic[32][32];
161 uint64_t rowcount;
162 uint8_t *p = picref->data[0];
163 int inti, intj;
164 int *intjlut;
165
166 uint64_t conflist[DIFFELEM_SIZE];
167 int f = 0, g = 0, w = 0;
168 int32_t dh1 = 1, dh2 = 1, dw1 = 1, dw2 = 1, a, b;
169 int64_t denom;
170 int i, j, k, ternary;
171 uint64_t blocksum;
172 int blocksize;
173 int64_t th; /* threshold */
174 int64_t sum;
175
176 int64_t precfactor = (sc->divide) ? 65536 : BLOCK_LCM;
177
178 /* initialize fs */
179 if (sc->curfinesig) {
180 fs = av_mallocz(sizeof(FineSignature));
181 if (!fs)
182 return AVERROR(ENOMEM);
183 sc->curfinesig->next = fs;
184 fs->prev = sc->curfinesig;
185 sc->curfinesig = fs;
186 } else {
187 fs = sc->curfinesig = sc->finesiglist;
188 sc->curcoarsesig1->first = fs;
189 }
190
191 fs->pts = picref->pts;
192 fs->index = sc->lastindex++;
193
194 memset(intpic, 0, sizeof(uint64_t)*32*32);
195 intjlut = av_malloc_array(inlink->w, sizeof(int));
196 if (!intjlut)
197 return AVERROR(ENOMEM);
198 for (i = 0; i < inlink->w; i++) {
199 intjlut[i] = (i*32)/inlink->w;
200 }
201
202 for (i = 0; i < inlink->h; i++) {
203 inti = (i*32)/inlink->h;
204 for (j = 0; j < inlink->w; j++) {
205 intj = intjlut[j];
206 intpic[inti][intj] += p[j];
207 }
208 p += picref->linesize[0];
209 }
210 av_freep(&intjlut);
211
212 /* The following calculates a summed area table (intpic) and brings the numbers
213 * in intpic to the same denominator.
214 * So you only have to handle the numinator in the following sections.
215 */
216 dh1 = inlink->h / 32;
217 if (inlink->h % 32)
218 dh2 = dh1 + 1;
219 dw1 = inlink->w / 32;
220 if (inlink->w % 32)
221 dw2 = dw1 + 1;
222 denom = (sc->divide) ? dh1 * (int64_t)dh2 * dw1 * dw2 : 1;
223
224 for (i = 0; i < 32; i++) {
225 rowcount = 0;
226 a = 1;
227 if (dh2 > 1) {
228 a = ((inlink->h*(i+1))%32 == 0) ? (inlink->h*(i+1))/32 - 1 : (inlink->h*(i+1))/32;
229 a -= ((inlink->h*i)%32 == 0) ? (inlink->h*i)/32 - 1 : (inlink->h*i)/32;
230 a = (a == dh1)? dh2 : dh1;
231 }
232 for (j = 0; j < 32; j++) {
233 b = 1;
234 if (dw2 > 1) {
235 b = ((inlink->w*(j+1))%32 == 0) ? (inlink->w*(j+1))/32 - 1 : (inlink->w*(j+1))/32;
236 b -= ((inlink->w*j)%32 == 0) ? (inlink->w*j)/32 - 1 : (inlink->w*j)/32;
237 b = (b == dw1)? dw2 : dw1;
238 }
239 rowcount += intpic[i][j] * a * b * precfactor / denom;
240 if (i > 0) {
241 intpic[i][j] = intpic[i-1][j] + rowcount;
242 } else {
243 intpic[i][j] = rowcount;
244 }
245 }
246 }
247
248 denom = (sc->divide) ? 1 : dh1 * (int64_t)dh2 * dw1 * dw2;
249
250 for (i = 0; i < ELEMENT_COUNT; i++) {
251 const ElemCat* elemcat = elements[i];
252 int64_t* elemsignature;
253 uint64_t* sortsignature;
254
255 elemsignature = av_malloc_array(elemcat->elem_count, sizeof(int64_t));
256 if (!elemsignature)
257 return AVERROR(ENOMEM);
258 sortsignature = av_malloc_array(elemcat->elem_count, sizeof(int64_t));
259 if (!sortsignature) {
260 av_freep(&elemsignature);
261 return AVERROR(ENOMEM);
262 }
263
264 for (j = 0; j < elemcat->elem_count; j++) {
265 blocksum = 0;
266 blocksize = 0;
267 for (k = 0; k < elemcat->left_count; k++) {
268 blocksum += get_block_sum(sc, intpic, &elemcat->blocks[j*elemcat->block_count+k]);
269 blocksize += get_block_size(&elemcat->blocks[j*elemcat->block_count+k]);
270 }
271 sum = blocksum / blocksize;
272 if (elemcat->av_elem) {
273 sum -= 128 * precfactor * denom;
274 } else {
275 blocksum = 0;
276 blocksize = 0;
277 for (; k < elemcat->block_count; k++) {
278 blocksum += get_block_sum(sc, intpic, &elemcat->blocks[j*elemcat->block_count+k]);
279 blocksize += get_block_size(&elemcat->blocks[j*elemcat->block_count+k]);
280 }
281 sum -= blocksum / blocksize;
282 conflist[g++] = FFABS(sum * 8 / (precfactor * denom));
283 }
284
285 elemsignature[j] = sum;
286 sortsignature[j] = FFABS(sum);
287 }
288
289 /* get threshold */
290 qsort(sortsignature, elemcat->elem_count, sizeof(uint64_t), cmp);
291 th = sortsignature[(int) (elemcat->elem_count*0.333)];
292
293 /* ternarize */
294 for (j = 0; j < elemcat->elem_count; j++) {
295 if (elemsignature[j] < -th) {
296 ternary = 0;
297 } else if (elemsignature[j] <= th) {
298 ternary = 1;
299 } else {
300 ternary = 2;
301 }
302 fs->framesig[f/5] += ternary * pot3[f%5];
303
304 if (f == wordvec[w]) {
305 fs->words[s2usw[w]/5] += ternary * pot3[wordt2b[s2usw[w]/5]++];
306 if (w < 24)
307 w++;
308 }
309 f++;
310 }
311 av_freep(&elemsignature);
312 av_freep(&sortsignature);
313 }
314
315 /* confidence */
316 qsort(conflist, DIFFELEM_SIZE, sizeof(uint64_t), cmp);
317 fs->confidence = FFMIN(conflist[DIFFELEM_SIZE/2], 255);
318
319 /* coarsesignature */
320 if (sc->coarsecount == 0) {
321 if (sc->curcoarsesig2) {
322 sc->curcoarsesig1 = av_mallocz(sizeof(CoarseSignature));
323 if (!sc->curcoarsesig1)
324 return AVERROR(ENOMEM);
325 sc->curcoarsesig1->first = fs;
326 sc->curcoarsesig2->next = sc->curcoarsesig1;
327 sc->coarseend = sc->curcoarsesig1;
328 }
329 }
330 if (sc->coarsecount == 45) {
331 sc->midcoarse = 1;
332 sc->curcoarsesig2 = av_mallocz(sizeof(CoarseSignature));
333 if (!sc->curcoarsesig2)
334 return AVERROR(ENOMEM);
335 sc->curcoarsesig2->first = fs;
336 sc->curcoarsesig1->next = sc->curcoarsesig2;
337 sc->coarseend = sc->curcoarsesig2;
338 }
339 for (i = 0; i < 5; i++) {
340 set_bit(sc->curcoarsesig1->data[i], fs->words[i]);
341 }
342 /* assuming the actual frame is the last */
343 sc->curcoarsesig1->last = fs;
344 if (sc->midcoarse) {
345 for (i = 0; i < 5; i++) {
346 set_bit(sc->curcoarsesig2->data[i], fs->words[i]);
347 }
348 sc->curcoarsesig2->last = fs;
349 }
350
351 sc->coarsecount = (sc->coarsecount+1)%90;
352
353 /* debug printing finesignature */
354 if (av_log_get_level() == AV_LOG_DEBUG) {
355 av_log(ctx, AV_LOG_DEBUG, "input %d, confidence: %d\n", FF_INLINK_IDX(inlink), fs->confidence);
356
357 av_log(ctx, AV_LOG_DEBUG, "words:");
358 for (i = 0; i < 5; i++) {
359 av_log(ctx, AV_LOG_DEBUG, " %d:", fs->words[i] );
360 av_log(ctx, AV_LOG_DEBUG, " %d", fs->words[i] / pot3[0] );
361 for (j = 1; j < 5; j++)
362 av_log(ctx, AV_LOG_DEBUG, ",%d", fs->words[i] % pot3[j-1] / pot3[j] );
363 av_log(ctx, AV_LOG_DEBUG, ";");
364 }
365 av_log(ctx, AV_LOG_DEBUG, "\n");
366
367 av_log(ctx, AV_LOG_DEBUG, "framesignature:");
368 for (i = 0; i < SIGELEM_SIZE/5; i++) {
369 av_log(ctx, AV_LOG_DEBUG, " %d", fs->framesig[i] / pot3[0] );
370 for (j = 1; j < 5; j++)
371 av_log(ctx, AV_LOG_DEBUG, ",%d", fs->framesig[i] % pot3[j-1] / pot3[j] );
372 }
373 av_log(ctx, AV_LOG_DEBUG, "\n");
374 }
375
376 if (FF_INLINK_IDX(inlink) == 0)
377 return ff_filter_frame(inlink->dst->outputs[0], picref);
378 return 1;
379 }
380
xml_export(AVFilterContext * ctx,StreamContext * sc,const char * filename)381 static int xml_export(AVFilterContext *ctx, StreamContext *sc, const char* filename)
382 {
383 FineSignature* fs;
384 CoarseSignature* cs;
385 int i, j;
386 FILE* f;
387 unsigned int pot3[5] = { 3*3*3*3, 3*3*3, 3*3, 3, 1 };
388
389 if (!sc->coarseend->last)
390 return AVERROR(EINVAL); // No frames ?
391
392 f = avpriv_fopen_utf8(filename, "w");
393 if (!f) {
394 int err = AVERROR(EINVAL);
395 char buf[128];
396 av_strerror(err, buf, sizeof(buf));
397 av_log(ctx, AV_LOG_ERROR, "cannot open xml file %s: %s\n", filename, buf);
398 return err;
399 }
400
401 /* header */
402 fprintf(f, "<?xml version='1.0' encoding='ASCII' ?>\n");
403 fprintf(f, "<Mpeg7 xmlns=\"urn:mpeg:mpeg7:schema:2001\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:mpeg:mpeg7:schema:2001 schema/Mpeg7-2001.xsd\">\n");
404 fprintf(f, " <DescriptionUnit xsi:type=\"DescriptorCollectionType\">\n");
405 fprintf(f, " <Descriptor xsi:type=\"VideoSignatureType\">\n");
406 fprintf(f, " <VideoSignatureRegion>\n");
407 fprintf(f, " <VideoSignatureSpatialRegion>\n");
408 fprintf(f, " <Pixel>0 0 </Pixel>\n");
409 fprintf(f, " <Pixel>%d %d </Pixel>\n", sc->w - 1, sc->h - 1);
410 fprintf(f, " </VideoSignatureSpatialRegion>\n");
411 fprintf(f, " <StartFrameOfSpatialRegion>0</StartFrameOfSpatialRegion>\n");
412 /* hoping num is 1, other values are vague */
413 fprintf(f, " <MediaTimeUnit>%d</MediaTimeUnit>\n", sc->time_base.den / sc->time_base.num);
414 fprintf(f, " <MediaTimeOfSpatialRegion>\n");
415 fprintf(f, " <StartMediaTimeOfSpatialRegion>0</StartMediaTimeOfSpatialRegion>\n");
416 fprintf(f, " <EndMediaTimeOfSpatialRegion>%" PRIu64 "</EndMediaTimeOfSpatialRegion>\n", sc->coarseend->last->pts);
417 fprintf(f, " </MediaTimeOfSpatialRegion>\n");
418
419 /* coarsesignatures */
420 for (cs = sc->coarsesiglist; cs; cs = cs->next) {
421 fprintf(f, " <VSVideoSegment>\n");
422 fprintf(f, " <StartFrameOfSegment>%" PRIu32 "</StartFrameOfSegment>\n", cs->first->index);
423 fprintf(f, " <EndFrameOfSegment>%" PRIu32 "</EndFrameOfSegment>\n", cs->last->index);
424 fprintf(f, " <MediaTimeOfSegment>\n");
425 fprintf(f, " <StartMediaTimeOfSegment>%" PRIu64 "</StartMediaTimeOfSegment>\n", cs->first->pts);
426 fprintf(f, " <EndMediaTimeOfSegment>%" PRIu64 "</EndMediaTimeOfSegment>\n", cs->last->pts);
427 fprintf(f, " </MediaTimeOfSegment>\n");
428 for (i = 0; i < 5; i++) {
429 fprintf(f, " <BagOfWords>");
430 for (j = 0; j < 31; j++) {
431 uint8_t n = cs->data[i][j];
432 if (j < 30) {
433 fprintf(f, "%d %d %d %d %d %d %d %d ", (n & 0x80) >> 7,
434 (n & 0x40) >> 6,
435 (n & 0x20) >> 5,
436 (n & 0x10) >> 4,
437 (n & 0x08) >> 3,
438 (n & 0x04) >> 2,
439 (n & 0x02) >> 1,
440 (n & 0x01));
441 } else {
442 /* print only 3 bit in last byte */
443 fprintf(f, "%d %d %d ", (n & 0x80) >> 7,
444 (n & 0x40) >> 6,
445 (n & 0x20) >> 5);
446 }
447 }
448 fprintf(f, "</BagOfWords>\n");
449 }
450 fprintf(f, " </VSVideoSegment>\n");
451 }
452
453 /* finesignatures */
454 for (fs = sc->finesiglist; fs; fs = fs->next) {
455 fprintf(f, " <VideoFrame>\n");
456 fprintf(f, " <MediaTimeOfFrame>%" PRIu64 "</MediaTimeOfFrame>\n", fs->pts);
457 /* confidence */
458 fprintf(f, " <FrameConfidence>%d</FrameConfidence>\n", fs->confidence);
459 /* words */
460 fprintf(f, " <Word>");
461 for (i = 0; i < 5; i++) {
462 fprintf(f, "%d ", fs->words[i]);
463 if (i < 4) {
464 fprintf(f, " ");
465 }
466 }
467 fprintf(f, "</Word>\n");
468 /* framesignature */
469 fprintf(f, " <FrameSignature>");
470 for (i = 0; i< SIGELEM_SIZE/5; i++) {
471 if (i > 0) {
472 fprintf(f, " ");
473 }
474 fprintf(f, "%d ", fs->framesig[i] / pot3[0]);
475 for (j = 1; j < 5; j++)
476 fprintf(f, " %d ", fs->framesig[i] % pot3[j-1] / pot3[j] );
477 }
478 fprintf(f, "</FrameSignature>\n");
479 fprintf(f, " </VideoFrame>\n");
480 }
481 fprintf(f, " </VideoSignatureRegion>\n");
482 fprintf(f, " </Descriptor>\n");
483 fprintf(f, " </DescriptionUnit>\n");
484 fprintf(f, "</Mpeg7>\n");
485
486 fclose(f);
487 return 0;
488 }
489
binary_export(AVFilterContext * ctx,StreamContext * sc,const char * filename)490 static int binary_export(AVFilterContext *ctx, StreamContext *sc, const char* filename)
491 {
492 FILE* f;
493 FineSignature* fs;
494 CoarseSignature* cs;
495 uint32_t numofsegments = (sc->lastindex + 44)/45;
496 int i, j;
497 PutBitContext buf;
498 /* buffer + header + coarsesignatures + finesignature */
499 int len = (512 + 6 * 32 + 3*16 + 2 +
500 numofsegments * (4*32 + 1 + 5*243) +
501 sc->lastindex * (2 + 32 + 6*8 + 608)) / 8;
502 uint8_t* buffer = av_malloc_array(len, sizeof(uint8_t));
503 if (!buffer)
504 return AVERROR(ENOMEM);
505
506 f = avpriv_fopen_utf8(filename, "wb");
507 if (!f) {
508 int err = AVERROR(EINVAL);
509 char buf[128];
510 av_strerror(err, buf, sizeof(buf));
511 av_log(ctx, AV_LOG_ERROR, "cannot open file %s: %s\n", filename, buf);
512 av_freep(&buffer);
513 return err;
514 }
515 init_put_bits(&buf, buffer, len);
516
517 put_bits32(&buf, 1); /* NumOfSpatial Regions, only 1 supported */
518 put_bits(&buf, 1, 1); /* SpatialLocationFlag, always the whole image */
519 put_bits32(&buf, 0); /* PixelX,1 PixelY,1, 0,0 */
520 put_bits(&buf, 16, sc->w-1 & 0xFFFF); /* PixelX,2 */
521 put_bits(&buf, 16, sc->h-1 & 0xFFFF); /* PixelY,2 */
522 put_bits32(&buf, 0); /* StartFrameOfSpatialRegion */
523 put_bits32(&buf, sc->lastindex); /* NumOfFrames */
524 /* hoping num is 1, other values are vague */
525 /* den/num might be greater than 16 bit, so cutting it */
526 put_bits(&buf, 16, 0xFFFF & (sc->time_base.den / sc->time_base.num)); /* MediaTimeUnit */
527 put_bits(&buf, 1, 1); /* MediaTimeFlagOfSpatialRegion */
528 put_bits32(&buf, 0); /* StartMediaTimeOfSpatialRegion */
529 put_bits32(&buf, 0xFFFFFFFF & sc->coarseend->last->pts); /* EndMediaTimeOfSpatialRegion */
530 put_bits32(&buf, numofsegments); /* NumOfSegments */
531 /* coarsesignatures */
532 for (cs = sc->coarsesiglist; cs; cs = cs->next) {
533 put_bits32(&buf, cs->first->index); /* StartFrameOfSegment */
534 put_bits32(&buf, cs->last->index); /* EndFrameOfSegment */
535 put_bits(&buf, 1, 1); /* MediaTimeFlagOfSegment */
536 put_bits32(&buf, 0xFFFFFFFF & cs->first->pts); /* StartMediaTimeOfSegment */
537 put_bits32(&buf, 0xFFFFFFFF & cs->last->pts); /* EndMediaTimeOfSegment */
538 for (i = 0; i < 5; i++) {
539 /* put 243 bits ( = 7 * 32 + 19 = 8 * 28 + 19) into buffer */
540 for (j = 0; j < 30; j++) {
541 put_bits(&buf, 8, cs->data[i][j]);
542 }
543 put_bits(&buf, 3, cs->data[i][30] >> 5);
544 }
545 }
546 /* finesignatures */
547 put_bits(&buf, 1, 0); /* CompressionFlag, only 0 supported */
548 for (fs = sc->finesiglist; fs; fs = fs->next) {
549 put_bits(&buf, 1, 1); /* MediaTimeFlagOfFrame */
550 put_bits32(&buf, 0xFFFFFFFF & fs->pts); /* MediaTimeOfFrame */
551 put_bits(&buf, 8, fs->confidence); /* FrameConfidence */
552 for (i = 0; i < 5; i++) {
553 put_bits(&buf, 8, fs->words[i]); /* Words */
554 }
555 /* framesignature */
556 for (i = 0; i < SIGELEM_SIZE/5; i++) {
557 put_bits(&buf, 8, fs->framesig[i]);
558 }
559 }
560
561 flush_put_bits(&buf);
562 fwrite(buffer, 1, put_bytes_output(&buf), f);
563 fclose(f);
564 av_freep(&buffer);
565 return 0;
566 }
567
export(AVFilterContext * ctx,StreamContext * sc,int input)568 static int export(AVFilterContext *ctx, StreamContext *sc, int input)
569 {
570 SignatureContext* sic = ctx->priv;
571 char filename[1024];
572
573 if (sic->nb_inputs > 1) {
574 /* error already handled */
575 av_assert0(av_get_frame_filename(filename, sizeof(filename), sic->filename, input) == 0);
576 } else {
577 if (av_strlcpy(filename, sic->filename, sizeof(filename)) >= sizeof(filename))
578 return AVERROR(EINVAL);
579 }
580 if (sic->format == FORMAT_XML) {
581 return xml_export(ctx, sc, filename);
582 } else {
583 return binary_export(ctx, sc, filename);
584 }
585 }
586
request_frame(AVFilterLink * outlink)587 static int request_frame(AVFilterLink *outlink)
588 {
589 AVFilterContext *ctx = outlink->src;
590 SignatureContext *sic = ctx->priv;
591 StreamContext *sc, *sc2;
592 MatchingInfo match;
593 int i, j, ret;
594 int lookup = 1; /* indicates wheather EOF of all files is reached */
595
596 /* process all inputs */
597 for (i = 0; i < sic->nb_inputs; i++){
598 sc = &(sic->streamcontexts[i]);
599
600 ret = ff_request_frame(ctx->inputs[i]);
601
602 /* return if unexpected error occurs in input stream */
603 if (ret < 0 && ret != AVERROR_EOF)
604 return ret;
605
606 /* export signature at EOF */
607 if (ret == AVERROR_EOF && !sc->exported) {
608 /* export if wanted */
609 if (strlen(sic->filename) > 0) {
610 if (export(ctx, sc, i) < 0)
611 return ret;
612 }
613 sc->exported = 1;
614 }
615 lookup &= sc->exported;
616 }
617
618 /* signature lookup */
619 if (lookup && sic->mode != MODE_OFF) {
620 /* iterate over every pair */
621 for (i = 0; i < sic->nb_inputs; i++) {
622 sc = &(sic->streamcontexts[i]);
623 for (j = i+1; j < sic->nb_inputs; j++) {
624 sc2 = &(sic->streamcontexts[j]);
625 match = lookup_signatures(ctx, sic, sc, sc2, sic->mode);
626 if (match.score != 0) {
627 av_log(ctx, AV_LOG_INFO, "matching of video %d at %f and %d at %f, %d frames matching\n",
628 i, ((double) match.first->pts * sc->time_base.num) / sc->time_base.den,
629 j, ((double) match.second->pts * sc2->time_base.num) / sc2->time_base.den,
630 match.matchframes);
631 if (match.whole)
632 av_log(ctx, AV_LOG_INFO, "whole video matching\n");
633 } else {
634 av_log(ctx, AV_LOG_INFO, "no matching of video %d and %d\n", i, j);
635 }
636 }
637 }
638 }
639
640 return ret;
641 }
642
init(AVFilterContext * ctx)643 static av_cold int init(AVFilterContext *ctx)
644 {
645
646 SignatureContext *sic = ctx->priv;
647 StreamContext *sc;
648 int i, ret;
649 char tmp[1024];
650
651 sic->streamcontexts = av_mallocz(sic->nb_inputs * sizeof(StreamContext));
652 if (!sic->streamcontexts)
653 return AVERROR(ENOMEM);
654
655 for (i = 0; i < sic->nb_inputs; i++) {
656 AVFilterPad pad = {
657 .type = AVMEDIA_TYPE_VIDEO,
658 .name = av_asprintf("in%d", i),
659 .config_props = config_input,
660 .filter_frame = filter_frame,
661 };
662
663 if (!pad.name)
664 return AVERROR(ENOMEM);
665 if ((ret = ff_append_inpad_free_name(ctx, &pad)) < 0)
666 return ret;
667
668 sc = &(sic->streamcontexts[i]);
669
670 sc->lastindex = 0;
671 sc->finesiglist = av_mallocz(sizeof(FineSignature));
672 if (!sc->finesiglist)
673 return AVERROR(ENOMEM);
674 sc->curfinesig = NULL;
675
676 sc->coarsesiglist = av_mallocz(sizeof(CoarseSignature));
677 if (!sc->coarsesiglist)
678 return AVERROR(ENOMEM);
679 sc->curcoarsesig1 = sc->coarsesiglist;
680 sc->coarseend = sc->coarsesiglist;
681 sc->coarsecount = 0;
682 sc->midcoarse = 0;
683 }
684
685 /* check filename */
686 if (sic->nb_inputs > 1 && strlen(sic->filename) > 0 && av_get_frame_filename(tmp, sizeof(tmp), sic->filename, 0) == -1) {
687 av_log(ctx, AV_LOG_ERROR, "The filename must contain %%d or %%0nd, if you have more than one input.\n");
688 return AVERROR(EINVAL);
689 }
690
691 return 0;
692 }
693
694
695
uninit(AVFilterContext * ctx)696 static av_cold void uninit(AVFilterContext *ctx)
697 {
698 SignatureContext *sic = ctx->priv;
699 StreamContext *sc;
700 void* tmp;
701 FineSignature* finsig;
702 CoarseSignature* cousig;
703 int i;
704
705
706 /* free the lists */
707 if (sic->streamcontexts != NULL) {
708 for (i = 0; i < sic->nb_inputs; i++) {
709 sc = &(sic->streamcontexts[i]);
710 finsig = sc->finesiglist;
711 cousig = sc->coarsesiglist;
712
713 while (finsig) {
714 tmp = finsig;
715 finsig = finsig->next;
716 av_freep(&tmp);
717 }
718 sc->finesiglist = NULL;
719
720 while (cousig) {
721 tmp = cousig;
722 cousig = cousig->next;
723 av_freep(&tmp);
724 }
725 sc->coarsesiglist = NULL;
726 }
727 av_freep(&sic->streamcontexts);
728 }
729 }
730
config_output(AVFilterLink * outlink)731 static int config_output(AVFilterLink *outlink)
732 {
733 AVFilterContext *ctx = outlink->src;
734 AVFilterLink *inlink = ctx->inputs[0];
735
736 outlink->time_base = inlink->time_base;
737 outlink->frame_rate = inlink->frame_rate;
738 outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
739 outlink->w = inlink->w;
740 outlink->h = inlink->h;
741
742 return 0;
743 }
744
745 static const AVFilterPad signature_outputs[] = {
746 {
747 .name = "default",
748 .type = AVMEDIA_TYPE_VIDEO,
749 .request_frame = request_frame,
750 .config_props = config_output,
751 },
752 };
753
754 const AVFilter ff_vf_signature = {
755 .name = "signature",
756 .description = NULL_IF_CONFIG_SMALL("Calculate the MPEG-7 video signature"),
757 .priv_size = sizeof(SignatureContext),
758 .priv_class = &signature_class,
759 .init = init,
760 .uninit = uninit,
761 FILTER_OUTPUTS(signature_outputs),
762 .inputs = NULL,
763 FILTER_PIXFMTS_ARRAY(pix_fmts),
764 .flags = AVFILTER_FLAG_DYNAMIC_INPUTS,
765 };
766