Browse code

mov: Export spherical information

This implements Spherical Video V1 and V2, as described in the
spatial-media collection by Google.

Signed-off-by: Vittorio Giovara <vittorio.giovara@gmail.com>

Vittorio Giovara authored on 2016/11/12 07:47:44
Showing 4 changed files
... ...
@@ -6,6 +6,7 @@ version <next>:
6 6
 - add internal ebur128 library, remove external libebur128 dependency
7 7
 - Pro-MPEG CoP #3-R2 FEC protocol
8 8
 - premultiply video filter
9
+- Support for spherical videos
9 10
 
10 11
 version 3.2:
11 12
 - libopenmpt demuxer
... ...
@@ -24,6 +24,9 @@
24 24
 #ifndef AVFORMAT_ISOM_H
25 25
 #define AVFORMAT_ISOM_H
26 26
 
27
+#include "libavutil/spherical.h"
28
+#include "libavutil/stereo3d.h"
29
+
27 30
 #include "avio.h"
28 31
 #include "internal.h"
29 32
 #include "dv.h"
... ...
@@ -177,6 +180,10 @@ typedef struct MOVStreamContext {
177 177
     int stsd_count;
178 178
 
179 179
     int32_t *display_matrix;
180
+    AVStereo3D *stereo3d;
181
+    AVSphericalMapping *spherical;
182
+    size_t spherical_size;
183
+
180 184
     uint32_t format;
181 185
 
182 186
     int has_sidx;  // If there is an sidx entry for this stream.
... ...
@@ -42,6 +42,8 @@
42 42
 #include "libavutil/aes.h"
43 43
 #include "libavutil/aes_ctr.h"
44 44
 #include "libavutil/sha.h"
45
+#include "libavutil/spherical.h"
46
+#include "libavutil/stereo3d.h"
45 47
 #include "libavutil/timecode.h"
46 48
 #include "libavcodec/ac3tab.h"
47 49
 #include "libavcodec/flac.h"
... ...
@@ -4498,8 +4500,204 @@ static int mov_read_tmcd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
4498 4498
     return 0;
4499 4499
 }
4500 4500
 
4501
+static int mov_read_st3d(MOVContext *c, AVIOContext *pb, MOVAtom atom)
4502
+{
4503
+    AVStream *st;
4504
+    MOVStreamContext *sc;
4505
+    enum AVStereo3DType type;
4506
+    int mode;
4507
+
4508
+    if (c->fc->nb_streams < 1)
4509
+        return 0;
4510
+
4511
+    st = c->fc->streams[c->fc->nb_streams - 1];
4512
+    sc = st->priv_data;
4513
+
4514
+    if (atom.size < 5) {
4515
+        av_log(c->fc, AV_LOG_ERROR, "Empty stereoscopic video box\n");
4516
+        return AVERROR_INVALIDDATA;
4517
+    }
4518
+    avio_skip(pb, 4); /* version + flags */
4519
+
4520
+    mode = avio_r8(pb);
4521
+    switch (mode) {
4522
+    case 0:
4523
+        type = AV_STEREO3D_2D;
4524
+        break;
4525
+    case 1:
4526
+        type = AV_STEREO3D_TOPBOTTOM;
4527
+        break;
4528
+    case 2:
4529
+        type = AV_STEREO3D_SIDEBYSIDE;
4530
+        break;
4531
+    default:
4532
+        av_log(c->fc, AV_LOG_WARNING, "Unknown st3d mode value %d\n", mode);
4533
+        return 0;
4534
+    }
4535
+
4536
+    sc->stereo3d = av_stereo3d_alloc();
4537
+    if (!sc->stereo3d)
4538
+        return AVERROR(ENOMEM);
4539
+
4540
+    sc->stereo3d->type = type;
4541
+    return 0;
4542
+}
4543
+
4544
+static int mov_read_sv3d(MOVContext *c, AVIOContext *pb, MOVAtom atom)
4545
+{
4546
+    AVStream *st;
4547
+    MOVStreamContext *sc;
4548
+    int size;
4549
+    int32_t yaw, pitch, roll;
4550
+    uint32_t tag;
4551
+    enum AVSphericalProjection projection;
4552
+
4553
+    if (c->fc->nb_streams < 1)
4554
+        return 0;
4555
+
4556
+    st = c->fc->streams[c->fc->nb_streams - 1];
4557
+    sc = st->priv_data;
4558
+
4559
+    if (atom.size < 8) {
4560
+        av_log(c->fc, AV_LOG_ERROR, "Empty spherical video box\n");
4561
+        return AVERROR_INVALIDDATA;
4562
+    }
4563
+
4564
+    size = avio_rb32(pb);
4565
+    if (size > atom.size)
4566
+        return AVERROR_INVALIDDATA;
4567
+
4568
+    tag = avio_rl32(pb);
4569
+    if (tag != MKTAG('s','v','h','d')) {
4570
+        av_log(c->fc, AV_LOG_ERROR, "Missing spherical video header\n");
4571
+        return 0;
4572
+    }
4573
+    avio_skip(pb, 4); /*  version + flags */
4574
+    avio_skip(pb, avio_r8(pb)); /* metadata_source */
4575
+
4576
+    size = avio_rb32(pb);
4577
+    if (size > atom.size)
4578
+        return AVERROR_INVALIDDATA;
4579
+
4580
+    tag = avio_rl32(pb);
4581
+    if (tag != MKTAG('p','r','o','j')) {
4582
+        av_log(c->fc, AV_LOG_ERROR, "Missing projection box\n");
4583
+        return 0;
4584
+    }
4585
+
4586
+    size = avio_rb32(pb);
4587
+    if (size > atom.size)
4588
+        return AVERROR_INVALIDDATA;
4589
+
4590
+    tag = avio_rl32(pb);
4591
+    if (tag != MKTAG('p','r','h','d')) {
4592
+        av_log(c->fc, AV_LOG_ERROR, "Missing projection header box\n");
4593
+        return 0;
4594
+    }
4595
+    avio_skip(pb, 4); /*  version + flags */
4596
+
4597
+    /* 16.16 fixed point */
4598
+    yaw   = avio_rb32(pb);
4599
+    pitch = avio_rb32(pb);
4600
+    roll  = avio_rb32(pb);
4601
+
4602
+    size = avio_rb32(pb);
4603
+    if (size > atom.size)
4604
+        return AVERROR_INVALIDDATA;
4605
+
4606
+    tag = avio_rl32(pb);
4607
+    avio_skip(pb, 4); /*  version + flags */
4608
+    switch (tag) {
4609
+    case MKTAG('c','b','m','p'):
4610
+        projection = AV_SPHERICAL_CUBEMAP;
4611
+        break;
4612
+    case MKTAG('e','q','u','i'):
4613
+        projection = AV_SPHERICAL_EQUIRECTANGULAR;
4614
+        break;
4615
+    default:
4616
+        av_log(c->fc, AV_LOG_ERROR, "Unknown projection type\n");
4617
+        return 0;
4618
+    }
4619
+
4620
+    sc->spherical = av_spherical_alloc(&sc->spherical_size);
4621
+    if (!sc->spherical)
4622
+        return AVERROR(ENOMEM);
4623
+
4624
+    sc->spherical->projection = projection;
4625
+
4626
+    sc->spherical->yaw   = yaw;
4627
+    sc->spherical->pitch = pitch;
4628
+    sc->spherical->roll  = roll;
4629
+
4630
+    return 0;
4631
+}
4632
+
4633
+static int mov_parse_uuid_spherical(MOVStreamContext *sc, AVIOContext *pb, size_t len)
4634
+{
4635
+    int ret = 0;
4636
+    uint8_t *buffer = av_malloc(len + 1);
4637
+    const char *val;
4638
+
4639
+    if (!buffer)
4640
+        return AVERROR(ENOMEM);
4641
+    buffer[len] = '\0';
4642
+
4643
+    ret = ffio_read_size(pb, buffer, len);
4644
+    if (ret < 0)
4645
+        goto out;
4646
+
4647
+    /* Check for mandatory keys and values, try to support XML as best-effort */
4648
+    if (av_stristr(buffer, "<GSpherical:StitchingSoftware>") &&
4649
+        (val = av_stristr(buffer, "<GSpherical:Spherical>")) &&
4650
+        av_stristr(val, "true") &&
4651
+        (val = av_stristr(buffer, "<GSpherical:Stitched>")) &&
4652
+        av_stristr(val, "true") &&
4653
+        (val = av_stristr(buffer, "<GSpherical:ProjectionType>")) &&
4654
+        av_stristr(val, "equirectangular")) {
4655
+        sc->spherical = av_spherical_alloc(&sc->spherical_size);
4656
+        if (!sc->spherical)
4657
+            goto out;
4658
+
4659
+        sc->spherical->projection = AV_SPHERICAL_EQUIRECTANGULAR;
4660
+
4661
+        if (av_stristr(buffer, "<GSpherical:StereoMode>")) {
4662
+            enum AVStereo3DType mode;
4663
+
4664
+            if (av_stristr(buffer, "left-right"))
4665
+                mode = AV_STEREO3D_SIDEBYSIDE;
4666
+            else if (av_stristr(buffer, "top-bottom"))
4667
+                mode = AV_STEREO3D_TOPBOTTOM;
4668
+            else
4669
+                mode = AV_STEREO3D_2D;
4670
+
4671
+            sc->stereo3d = av_stereo3d_alloc();
4672
+            if (!sc->stereo3d)
4673
+                goto out;
4674
+
4675
+            sc->stereo3d->type = mode;
4676
+        }
4677
+
4678
+        /* orientation */
4679
+        val = av_stristr(buffer, "<GSpherical:InitialViewHeadingDegrees>");
4680
+        if (val)
4681
+            sc->spherical->yaw = strtol(val, NULL, 10) * (1 << 16);
4682
+        val = av_stristr(buffer, "<GSpherical:InitialViewPitchDegrees>");
4683
+        if (val)
4684
+            sc->spherical->pitch = strtol(val, NULL, 10) * (1 << 16);
4685
+        val = av_stristr(buffer, "<GSpherical:InitialViewRollDegrees>");
4686
+        if (val)
4687
+            sc->spherical->roll = strtol(val, NULL, 10) * (1 << 16);
4688
+    }
4689
+
4690
+out:
4691
+    av_free(buffer);
4692
+    return ret;
4693
+}
4694
+
4501 4695
 static int mov_read_uuid(MOVContext *c, AVIOContext *pb, MOVAtom atom)
4502 4696
 {
4697
+    AVStream *st;
4698
+    MOVStreamContext *sc;
4503 4699
     int ret;
4504 4700
     uint8_t uuid[16];
4505 4701
     static const uint8_t uuid_isml_manifest[] = {
... ...
@@ -4510,10 +4708,19 @@ static int mov_read_uuid(MOVContext *c, AVIOContext *pb, MOVAtom atom)
4510 4510
         0xbe, 0x7a, 0xcf, 0xcb, 0x97, 0xa9, 0x42, 0xe8,
4511 4511
         0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac
4512 4512
     };
4513
+    static const uint8_t uuid_spherical[] = {
4514
+        0xff, 0xcc, 0x82, 0x63, 0xf8, 0x55, 0x4a, 0x93,
4515
+        0x88, 0x14, 0x58, 0x7a, 0x02, 0x52, 0x1f, 0xdd,
4516
+    };
4513 4517
 
4514 4518
     if (atom.size < sizeof(uuid) || atom.size == INT64_MAX)
4515 4519
         return AVERROR_INVALIDDATA;
4516 4520
 
4521
+    if (c->fc->nb_streams < 1)
4522
+        return 0;
4523
+    st = c->fc->streams[c->fc->nb_streams - 1];
4524
+    sc = st->priv_data;
4525
+
4517 4526
     ret = avio_read(pb, uuid, sizeof(uuid));
4518 4527
     if (ret < 0) {
4519 4528
         return ret;
... ...
@@ -4585,7 +4792,14 @@ static int mov_read_uuid(MOVContext *c, AVIOContext *pb, MOVAtom atom)
4585 4585
             av_dict_set(&c->fc->metadata, "xmp", buffer, 0);
4586 4586
         }
4587 4587
         av_free(buffer);
4588
-    }
4588
+    } else if (!memcmp(uuid, uuid_spherical, sizeof(uuid))) {
4589
+        size_t len = atom.size - sizeof(uuid);
4590
+        ret = mov_parse_uuid_spherical(sc, pb, len);
4591
+        if (ret < 0)
4592
+            return ret;
4593
+        if (!sc->spherical)
4594
+            av_log(c->fc, AV_LOG_WARNING, "Invalid spherical metadata found\n");    }
4595
+
4589 4596
     return 0;
4590 4597
 }
4591 4598
 
... ...
@@ -4973,6 +5187,8 @@ static const MOVParseTableEntry mov_default_parse_table[] = {
4973 4973
 { MKTAG('s','e','n','c'), mov_read_senc },
4974 4974
 { MKTAG('s','a','i','z'), mov_read_saiz },
4975 4975
 { MKTAG('d','f','L','a'), mov_read_dfla },
4976
+{ MKTAG('s','t','3','d'), mov_read_st3d }, /* stereoscopic 3D video box */
4977
+{ MKTAG('s','v','3','d'), mov_read_sv3d }, /* spherical video box */
4976 4978
 { 0, NULL }
4977 4979
 };
4978 4980
 
... ...
@@ -5393,6 +5609,9 @@ static int mov_read_close(AVFormatContext *s)
5393 5393
         av_freep(&sc->cenc.auxiliary_info);
5394 5394
         av_freep(&sc->cenc.auxiliary_info_sizes);
5395 5395
         av_aes_ctr_free(sc->cenc.aes_ctr);
5396
+
5397
+        av_freep(&sc->stereo3d);
5398
+        av_freep(&sc->spherical);
5396 5399
     }
5397 5400
 
5398 5401
     if (mov->dv_demux) {
... ...
@@ -5711,6 +5930,24 @@ static int mov_read_header(AVFormatContext *s)
5711 5711
 
5712 5712
                 sc->display_matrix = NULL;
5713 5713
             }
5714
+            if (sc->stereo3d) {
5715
+                err = av_stream_add_side_data(st, AV_PKT_DATA_STEREO3D,
5716
+                                              (uint8_t *)sc->stereo3d,
5717
+                                              sizeof(*sc->stereo3d));
5718
+                if (err < 0)
5719
+                    return err;
5720
+
5721
+                sc->stereo3d = NULL;
5722
+            }
5723
+            if (sc->spherical) {
5724
+                err = av_stream_add_side_data(st, AV_PKT_DATA_SPHERICAL,
5725
+                                              (uint8_t *)sc->spherical,
5726
+                                              sc->spherical_size);
5727
+                if (err < 0)
5728
+                    return err;
5729
+
5730
+                sc->spherical = NULL;
5731
+            }
5714 5732
             break;
5715 5733
         }
5716 5734
     }
... ...
@@ -33,7 +33,7 @@
33 33
 // Also please add any ticket numbers that you believe might be affected here
34 34
 #define LIBAVFORMAT_VERSION_MAJOR  57
35 35
 #define LIBAVFORMAT_VERSION_MINOR  58
36
-#define LIBAVFORMAT_VERSION_MICRO 101
36
+#define LIBAVFORMAT_VERSION_MICRO 102
37 37
 
38 38
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
39 39
                                                LIBAVFORMAT_VERSION_MINOR, \