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>
... | ... |
@@ -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, \ |