shithub: dav1d

Download patch

ref: 46d092ae6ac62284e5bdde4d0808aca4ab7410a9
parent: eb4a8f6d076225fc5e75eed585535eccca1b3ace
author: Ronald S. Bultje <rsbultje@gmail.com>
date: Sat Nov 16 09:45:24 EST 2019

Add demuxer probing

This allows auto-detection between section5 and annexb files, which
share the same extension.

--- a/tools/input/annexb.c
+++ b/tools/input/annexb.c
@@ -1,6 +1,7 @@
 /*
  * Copyright © 2018, VideoLAN and dav1d authors
  * Copyright © 2018, Two Orioles, LLC
+ * Copyright © 2019, James Almer <jamrial@gmail.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -32,9 +33,92 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "common/intops.h"
+
+#include "dav1d/headers.h"
+
 #include "input/demuxer.h"
 #include "input/parse.h"
 
+// these functions are based on an implementation from FFmpeg, and relicensed
+// with author's permission
+
+#define PROBE_SIZE 1024
+
+static int annexb_probe(const uint8_t *data) {
+    int ret, cnt = 0;
+
+    size_t temporal_unit_size;
+    ret = leb(data + cnt, PROBE_SIZE - cnt, &temporal_unit_size);
+    if (ret < 0)
+        return 0;
+    cnt += ret;
+
+    size_t frame_unit_size;
+    ret = leb(data + cnt, PROBE_SIZE - cnt, &frame_unit_size);
+    if (ret < 0 || ((uint64_t)frame_unit_size + ret) > temporal_unit_size)
+        return 0;
+    cnt += ret;
+
+    temporal_unit_size -= ret;
+
+    size_t obu_unit_size;
+    ret = leb(data + cnt, PROBE_SIZE - cnt, &obu_unit_size);
+    if (ret < 0 || ((uint64_t)obu_unit_size + ret) >= frame_unit_size)
+        return 0;
+    cnt += ret;
+
+    temporal_unit_size -= obu_unit_size + ret;
+    frame_unit_size -= obu_unit_size + ret;
+
+    // Check that the first OBU is a Temporal Delimiter.
+    size_t obu_size;
+    enum Dav1dObuType type;
+    ret = parse_obu_header(data + cnt, imin(PROBE_SIZE - cnt, (int) obu_unit_size),
+                           &obu_size, &type, 1);
+    if (ret < 0 || type != DAV1D_OBU_TD || obu_size > 0)
+        return 0;
+    cnt += obu_unit_size;
+
+    // look for first frame and accompanying sequence header
+    int seq = 0;
+    while (cnt < PROBE_SIZE) {
+        ret = leb(data + cnt, PROBE_SIZE - cnt, &obu_unit_size);
+        if (ret < 0 || ((uint64_t)obu_unit_size + ret) > frame_unit_size)
+            return 0;
+        cnt += ret;
+        temporal_unit_size -= ret;
+        frame_unit_size -= ret;
+
+        ret = parse_obu_header(data + cnt, imin(PROBE_SIZE - cnt, (int) obu_unit_size),
+                               &obu_size, &type, 1);
+        if (ret < 0)
+            return 0;
+        cnt += obu_unit_size;
+
+        switch (type) {
+        case DAV1D_OBU_SEQ_HDR:
+            seq = 1;
+            break;
+        case DAV1D_OBU_FRAME:
+        case DAV1D_OBU_FRAME_HDR:
+            return seq;
+        case DAV1D_OBU_TD:
+        case DAV1D_OBU_TILE_GRP:
+            return 0;
+        default:
+            break;
+        }
+
+        temporal_unit_size -= obu_unit_size;
+        frame_unit_size -= obu_unit_size;
+        if (frame_unit_size <= 0)
+            break;
+    }
+
+    return 0;
+}
+
 typedef struct DemuxerPriv {
     FILE *f;
     size_t temporal_unit_size;
@@ -103,7 +187,8 @@
 const Demuxer annexb_demuxer = {
     .priv_data_size = sizeof(AnnexbInputContext),
     .name = "annexb",
-    .extension = "obu",
+    .probe = annexb_probe,
+    .probe_sz = PROBE_SIZE,
     .open = annexb_open,
     .read = annexb_read,
     .close = annexb_close,
--- a/tools/input/demuxer.h
+++ b/tools/input/demuxer.h
@@ -34,7 +34,8 @@
 typedef struct Demuxer {
     int priv_data_size;
     const char *name;
-    const char *extension;
+    int probe_sz;
+    int (*probe)(const uint8_t *data);
     int (*open)(DemuxerPriv *ctx, const char *filename,
                 unsigned fps[2], unsigned *num_frames, unsigned timebase[2]);
     int (*read)(DemuxerPriv *ctx, Dav1dData *data);
--- a/tools/input/input.c
+++ b/tools/input/input.c
@@ -33,6 +33,7 @@
 #include <string.h>
 
 #include "common/attributes.h"
+#include "common/intops.h"
 
 #include "input/input.h"
 #include "input/demuxer.h"
@@ -58,23 +59,6 @@
     register_demuxer(section5_demuxer);
 }
 
-static const char *find_extension(const char *const f) {
-    const size_t l = strlen(f);
-
-    if (l == 0) return NULL;
-
-    const char *const end = &f[l - 1], *step = end;
-    while ((*step >= 'a' && *step <= 'z') ||
-           (*step >= 'A' && *step <= 'Z') ||
-           (*step >= '0' && *step <= '9'))
-    {
-        step--;
-    }
-
-    return (step < end && step > f && *step == '.' && step[-1] != '/') ?
-           &step[1] : NULL;
-}
-
 int input_open(DemuxerContext **const c_out,
                const char *const name, const char *const filename,
                unsigned fps[2], unsigned *const num_frames, unsigned timebase[2])
@@ -95,22 +79,34 @@
             return DAV1D_ERR(ENOPROTOOPT);
         }
     } else {
-        const char *const ext = find_extension(filename);
-        if (!ext) {
-            fprintf(stderr, "No extension found for file %s\n", filename);
-            return -1;
+        int probe_sz = 0;
+        for (i = 0; i < num_demuxers; i++)
+            probe_sz = imax(probe_sz, demuxers[i]->probe_sz);
+        uint8_t *const probe_data = malloc(probe_sz);
+        if (!probe_data) {
+            fprintf(stderr, "Failed to allocate memory\n");
+            return DAV1D_ERR(ENOMEM);
         }
+        FILE *f = fopen(filename, "rb");
+        res = !!fread(probe_data, 1, probe_sz, f);
+        fclose(f);
+        if (!res) {
+            free(probe_data);
+            fprintf(stderr, "Failed to read probe data\n");
+            return errno ? DAV1D_ERR(errno) : DAV1D_ERR(ENODATA);
+        }
 
         for (i = 0; i < num_demuxers; i++) {
-            if (!strcmp(demuxers[i]->extension, ext)) {
+            if (demuxers[i]->probe(probe_data)) {
                 impl = demuxers[i];
                 break;
             }
         }
+        free(probe_data);
         if (i == num_demuxers) {
             fprintf(stderr,
-                    "Failed to find demuxer for file %s (\"%s\")\n",
-                    filename, ext);
+                    "Failed to probe demuxer for file %s\n",
+                    filename);
             return DAV1D_ERR(ENOPROTOOPT);
         }
     }
--- a/tools/input/ivf.c
+++ b/tools/input/ivf.c
@@ -39,6 +39,16 @@
     FILE *f;
 } IvfInputContext;
 
+static const uint8_t probe_data[] = {
+    'D', 'K', 'I', 'F',
+    0, 0, 0x20, 0,
+    'A', 'V', '0', '1',
+};
+
+static int ivf_probe(const uint8_t *const data) {
+    return !memcmp(data, probe_data, sizeof(probe_data));
+}
+
 static unsigned rl32(const uint8_t *const p) {
     return ((uint32_t)p[3] << 24U) | (p[2] << 16U) | (p[1] << 8U) | p[0];
 }
@@ -121,7 +131,8 @@
 const Demuxer ivf_demuxer = {
     .priv_data_size = sizeof(IvfInputContext),
     .name = "ivf",
-    .extension = "ivf",
+    .probe = ivf_probe,
+    .probe_sz = sizeof(probe_data),
     .open = ivf_open,
     .read = ivf_read,
     .close = ivf_close,
--- a/tools/input/parse.h
+++ b/tools/input/parse.h
@@ -48,4 +48,60 @@
     return i;
 }
 
+// these functions are based on an implementation from FFmpeg, and relicensed
+// with author's permission
+
+static int leb(const uint8_t *ptr, int sz, size_t *const len) {
+    unsigned i = 0, more;
+    *len = 0;
+    do {
+        if (!sz--) return -1;
+        const int byte = *ptr++;
+        more = byte & 0x80;
+        const unsigned bits = byte & 0x7f;
+        if (i <= 3 || (i == 4 && bits < (1 << 4)))
+            *len |= bits << (i * 7);
+        else if (bits) return -1;
+        if (++i == 8 && more) return -1;
+    } while (more);
+    return i;
+}
+
+static inline int parse_obu_header(const uint8_t *buf, int buf_size,
+                                   size_t *const obu_size,
+                                   enum Dav1dObuType *const type,
+                                   const int allow_implicit_size)
+{
+    int ret, extension_flag, has_size_flag;
+
+    if (!buf_size)
+        return -1;
+    if (*buf & 0x80) // obu_forbidden_bit
+        return -1;
+
+    *type = (*buf & 0x78) >> 3;
+    extension_flag = (*buf & 0x4) >> 2;
+    has_size_flag  = (*buf & 0x2) >> 1;
+    // ignore obu_reserved_1bit
+    buf++;
+    buf_size--;
+
+    if (extension_flag) {
+        buf++;
+        buf_size--;
+        // ignore fields
+    }
+
+    if (has_size_flag) {
+        ret = leb(buf, buf_size, obu_size);
+        if (ret < 0)
+            return -1;
+        return (int) *obu_size + ret + 1 + extension_flag;
+    } else if (!allow_implicit_size)
+        return -1;
+
+    *obu_size = buf_size;
+    return buf_size + 1 + extension_flag;
+}
+
 #endif /* DAV1D_INPUT_PARSE_H */
--- a/tools/input/section5.c
+++ b/tools/input/section5.c
@@ -1,6 +1,7 @@
 /*
  * Copyright © 2019, VideoLAN and dav1d authors
  * Copyright © 2019, Two Orioles, LLC
+ * Copyright © 2019, James Almer <jamrial@gmail.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -32,9 +33,52 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "dav1d/headers.h"
+
 #include "input/demuxer.h"
 #include "input/parse.h"
 
+#define PROBE_SIZE 1024
+
+static int section5_probe(const uint8_t *data) {
+    int ret, cnt = 0;
+
+    // Check that the first OBU is a Temporal Delimiter.
+    size_t obu_size;
+    enum Dav1dObuType type;
+    ret = parse_obu_header(data + cnt, PROBE_SIZE - cnt,
+                           &obu_size, &type, 0);
+    if (ret < 0 || type != DAV1D_OBU_TD || obu_size > 0)
+        return 0;
+    cnt += ret;
+
+    // look for first frame and accompanying sequence header
+    int seq = 0;
+    while (cnt < PROBE_SIZE) {
+        ret = parse_obu_header(data + cnt, PROBE_SIZE - cnt,
+                               &obu_size, &type, 0);
+        if (ret < 0)
+            return 0;
+        cnt += ret;
+
+        switch (type) {
+        case DAV1D_OBU_SEQ_HDR:
+            seq = 1;
+            break;
+        case DAV1D_OBU_FRAME:
+        case DAV1D_OBU_FRAME_HDR:
+            return seq;
+        case DAV1D_OBU_TD:
+        case DAV1D_OBU_TILE_GRP:
+            return 0;
+        default:
+            break;
+        }
+    }
+
+    return 0;
+}
+
 typedef struct DemuxerPriv {
     FILE *f;
 } Section5InputContext;
@@ -132,7 +176,8 @@
 const Demuxer section5_demuxer = {
     .priv_data_size = sizeof(Section5InputContext),
     .name = "section5",
-    .extension = "obu",
+    .probe = section5_probe,
+    .probe_sz = PROBE_SIZE,
     .open = section5_open,
     .read = section5_read,
     .close = section5_close,