From patchwork Fri Nov 11 22:49:02 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vittorio Giovara X-Patchwork-Id: 1390 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.90.1 with SMTP id o1csp14748vsb; Fri, 11 Nov 2016 15:06:21 -0800 (PST) X-Received: by 10.28.129.86 with SMTP id c83mr298094wmd.53.1478905581453; Fri, 11 Nov 2016 15:06:21 -0800 (PST) Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id g29si13336773wmi.66.2016.11.11.15.06.17; Fri, 11 Nov 2016 15:06:21 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 8200D689F1F; Sat, 12 Nov 2016 01:06:15 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-qk0-f196.google.com (mail-qk0-f196.google.com [209.85.220.196]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id B7845689DA7 for ; Sat, 12 Nov 2016 01:06:08 +0200 (EET) Received: by mail-qk0-f196.google.com with SMTP id x190so3545031qkb.0 for ; Fri, 11 Nov 2016 15:06:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:subject:date:message-id:in-reply-to:references; bh=t0K3qNQovH8WaV9pIs6m6cPo8XD4TWhtT66Axo1OJIE=; b=vmML1bVUysiqRqNqgHNFQy4mn64tcIIwaL1LlN8yqp6jwBI5jMK+cE3XXhfHgImmOP /k+KVLdap8O9s9lQr4LZB9pc28qdeHXD8rPkAqmgmjBzliHM/y/Fc7UoWNzx8wh/gNrW YpNQ5Fpf6KANLJH8l8zpy7Mq7TFo84mE7gG/Wv2PDrbmhkQbE60IQzs5aD3Rcx3Joexa rr2ITxreP0EMg2bkM1xqBdGUteWybX8phd4ZbtFJt0EIGi52TNKKLF5oDwy1rdVvOzuw 0Qq6x6OUQUr9JIucj8WRNFRpp+fw1KnBhnpls+5Ind1xfPRAdUZeLlORHyTgWWkQ8b3L LRKQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references; bh=t0K3qNQovH8WaV9pIs6m6cPo8XD4TWhtT66Axo1OJIE=; b=kt2S0QGOD8PHhjLIPgm/z00hs+UbUHm0Lr601fF70kcTMsNyRcXHz7QWzgmNFdtvrP 2NhrAc6NYeNTj0mmWqigYvyKRdgP1Nyp3+e3kalt3q8tKyv7Sf9JwqPq7xE3gKCghbFr 3UsGrBH/5Y6ndYdxNy11kl37gg9EqeBj8MYnn6CxHbPZIj96UPS1mQL/SE2ZCxKTYSN4 cl2k5gIH5kDiA+FZMYQBdnUMseHZtKRs/5lCjySClPZdDOQzdlKBwfU7mNajmPwCodB2 rOcwAh8npyEIHgxjvF8jjViaiSBwfiERQRk5gZcfRH03hvs7unYaFri2HMZBGpIKbLKK ndQQ== X-Gm-Message-State: ABUngvcabMb4W/iZXucp833jZqXgUYciGtz7fotgDoc0p9v72rYUOA8ypy9R9vogJOovXg== X-Received: by 10.55.74.1 with SMTP id x1mr6296574qka.316.1478904545457; Fri, 11 Nov 2016 14:49:05 -0800 (PST) Received: from vimacbookpro.vimeows.com (nyv-exweb.iac.com. [216.112.252.10]) by smtp.gmail.com with ESMTPSA id f21sm4894494qkh.17.2016.11.11.14.49.04 for (version=TLS1 cipher=AES128-SHA bits=128/128); Fri, 11 Nov 2016 14:49:04 -0800 (PST) From: Vittorio Giovara To: ffmpeg-devel@ffmpeg.org Date: Fri, 11 Nov 2016 17:49:02 -0500 Message-Id: <20161111224902.83581-3-vittorio.giovara@gmail.com> X-Mailer: git-send-email 2.10.0 In-Reply-To: <20161111224902.83581-1-vittorio.giovara@gmail.com> References: <20161111224902.83581-1-vittorio.giovara@gmail.com> Subject: [FFmpeg-devel] [PATCH 3/3] mov: Export spherical information X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" This implements Spherical Video V1 and V2, as described in the spatial-media collection by Google. Signed-off-by: Vittorio Giovara --- Please CC. Vittorio libavformat/isom.h | 7 ++ libavformat/mov.c | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 287 insertions(+), 1 deletion(-) diff --git a/libavformat/isom.h b/libavformat/isom.h index 02bfedd..0fd9eb0 100644 --- a/libavformat/isom.h +++ b/libavformat/isom.h @@ -24,6 +24,9 @@ #ifndef AVFORMAT_ISOM_H #define AVFORMAT_ISOM_H +#include "libavutil/spherical.h" +#include "libavutil/stereo3d.h" + #include "avio.h" #include "internal.h" #include "dv.h" @@ -177,6 +180,10 @@ typedef struct MOVStreamContext { int stsd_count; int32_t *display_matrix; + AVStereo3D *stereo3d; + AVSphericalMapping *spherical; + size_t spherical_size; + uint32_t format; int has_sidx; // If there is an sidx entry for this stream. diff --git a/libavformat/mov.c b/libavformat/mov.c index cb3c61c..d36c935 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -42,6 +42,8 @@ #include "libavutil/aes.h" #include "libavutil/aes_ctr.h" #include "libavutil/sha.h" +#include "libavutil/spherical.h" +#include "libavutil/stereo3d.h" #include "libavutil/timecode.h" #include "libavcodec/ac3tab.h" #include "libavcodec/mpegaudiodecheader.h" @@ -4498,8 +4500,230 @@ static int mov_read_tmcd(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; } +static int mov_read_st3d(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + AVStream *st; + MOVStreamContext *sc; + enum AVStereo3DType type; + int mode; + + if (c->fc->nb_streams < 1) + return 0; + + st = c->fc->streams[c->fc->nb_streams - 1]; + sc = st->priv_data; + + if (atom.size < 1) { + av_log(c->fc, AV_LOG_ERROR, "Empty stereoscopic video box\n"); + return AVERROR_INVALIDDATA; + } + + mode = avio_r8(pb); + switch (mode) { + case 0: + type = AV_STEREO3D_2D; + break; + case 1: + type = AV_STEREO3D_TOPBOTTOM; + break; + case 2: + type = AV_STEREO3D_SIDEBYSIDE; + break; + default: + av_log(c->fc, AV_LOG_WARNING, "Unknown st3d mode value %d\n", mode); + return 0; + } + + sc->stereo3d = av_stereo3d_alloc(); + if (!sc->stereo3d) + return AVERROR(ENOMEM); + + sc->stereo3d->type = type; + return 0; +} + +static int mov_read_sv3d(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + AVStream *st; + MOVStreamContext *sc; + int size; + int32_t yaw, pitch, roll; + uint32_t tag; + unsigned l, t, r, b; + enum AVSphericalProjection projection; + + if (c->fc->nb_streams < 1) + return 0; + + st = c->fc->streams[c->fc->nb_streams - 1]; + sc = st->priv_data; + + if (atom.size < 4) { + av_log(c->fc, AV_LOG_ERROR, "Empty spherical video box\n"); + return AVERROR_INVALIDDATA; + } + + size = avio_rb32(pb); + if (size > atom.size) + return AVERROR_INVALIDDATA; + + tag = avio_rl32(pb); + if (tag != MKTAG('s','v','h','d')) { + av_log(c->fc, AV_LOG_ERROR, "Missing spherical video header\n"); + return 0; + } + avio_skip(pb, size - 8); /* metadata_source */ + + size = avio_rb32(pb); + if (size > atom.size) + return AVERROR_INVALIDDATA; + + tag = avio_rl32(pb); + if (tag != MKTAG('p','r','o','j')) { + av_log(c->fc, AV_LOG_ERROR, "Missing projection box\n"); + return 0; + } + + size = avio_rb32(pb); + if (size > atom.size) + return AVERROR_INVALIDDATA; + + tag = avio_rl32(pb); + if (tag != MKTAG('p','r','h','d')) { + av_log(c->fc, AV_LOG_ERROR, "Missing projection header box\n"); + return 0; + } + + /* 16.16 fixed point */ + yaw = avio_rb32(pb); + pitch = avio_rb32(pb); + roll = avio_rb32(pb); + + avio_skip(pb, size - 20); + + size = avio_rb32(pb); + if (size > atom.size) + return AVERROR_INVALIDDATA; + + tag = avio_rl32(pb); + switch (tag) { + case MKTAG('c','b','m','p'): + projection = AV_SPHERICAL_CUBEMAP; + avio_skip(pb, 4); /* layout */ + l = t = r = b = avio_rb32(pb); + break; + case MKTAG('e','q','u','i'): + projection = AV_SPHERICAL_EQUIRECTANGULAR; + t = avio_rb32(pb); + b = avio_rb32(pb); + l = avio_rb32(pb); + r = avio_rb32(pb); + break; + default: + av_log(c->fc, AV_LOG_ERROR, "Unknown projection type\n"); + return 0; + } + + sc->spherical = av_spherical_alloc(&sc->spherical_size); + if (!sc->spherical) + return AVERROR(ENOMEM); + + sc->spherical->projection = projection; + + sc->spherical->yaw = ((double) yaw) / (1 << 16); + sc->spherical->pitch = ((double) pitch) / (1 << 16); + sc->spherical->roll = ((double) roll) / (1 << 16); + + sc->spherical->left_offset = l; + sc->spherical->top_offset = t; + sc->spherical->right_offset = r; + sc->spherical->bottom_offset = b; + + return 0; +} + +static int mov_parse_uuid_spherical(MOVStreamContext *sc, AVIOContext *pb, size_t len) +{ + int ret = 0; + uint8_t *buffer = av_malloc(len + 1); + const char *val; + + if (!buffer) + return AVERROR(ENOMEM); + buffer[len] = '\0'; + + ret = ffio_read_size(pb, buffer, len); + if (ret < 0) + goto out; + + /* Check for mandatory keys and values, try to support XML as best-effort */ + if (av_stristr(buffer, "") && + (val = av_stristr(buffer, "")) && + av_stristr(val, "true") && + (val = av_stristr(buffer, "")) && + av_stristr(val, "true") && + (val = av_stristr(buffer, "")) && + av_stristr(val, "equirectangular")) { + sc->spherical = av_spherical_alloc(&sc->spherical_size); + if (!sc->spherical) + goto out; + + sc->spherical->projection = AV_SPHERICAL_EQUIRECTANGULAR; + + if (av_stristr(buffer, "")) { + enum AVStereo3DType mode; + + if (av_stristr(buffer, "left-right")) + mode = AV_STEREO3D_SIDEBYSIDE; + else if (av_stristr(buffer, "top-bottom")) + mode = AV_STEREO3D_TOPBOTTOM; + else + mode = AV_STEREO3D_2D; + + sc->stereo3d = av_stereo3d_alloc(); + if (!sc->stereo3d) + goto out; + + sc->stereo3d->type = mode; + } + + /* orientation */ + val = av_stristr(buffer, ""); + if (val) + sc->spherical->yaw = strtol(val, NULL, 10); + val = av_stristr(buffer, ""); + if (val) + sc->spherical->pitch = strtol(val, NULL, 10); + val = av_stristr(buffer, ""); + if (val) + sc->spherical->roll = strtol(val, NULL, 10); + + /* cropping */ + val = av_stristr(buffer, ""); + if (val) + sc->spherical->left_offset = strtol(val, NULL, 10); + val = av_stristr(buffer, ""); + if (val) + sc->spherical->top_offset = strtol(val, NULL, 10); + val = av_stristr(buffer, ""); + if (val) + sc->spherical->right_offset = + sc->width - sc->spherical->left_offset - strtol(val, NULL, 10); + val = av_stristr(buffer, ""); + if (val) + sc->spherical->bottom_offset = + sc->height - sc->spherical->top_offset - strtol(val, NULL, 10); + } + +out: + av_free(buffer); + return ret; +} + static int mov_read_uuid(MOVContext *c, AVIOContext *pb, MOVAtom atom) { + AVStream *st; + MOVStreamContext *sc; int ret; uint8_t uuid[16]; static const uint8_t uuid_isml_manifest[] = { @@ -4510,10 +4734,19 @@ static int mov_read_uuid(MOVContext *c, AVIOContext *pb, MOVAtom atom) 0xbe, 0x7a, 0xcf, 0xcb, 0x97, 0xa9, 0x42, 0xe8, 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac }; + static const uint8_t uuid_spherical[] = { + 0xff, 0xcc, 0x82, 0x63, 0xf8, 0x55, 0x4a, 0x93, + 0x88, 0x14, 0x58, 0x7a, 0x02, 0x52, 0x1f, 0xdd, + }; if (atom.size < sizeof(uuid) || atom.size == INT64_MAX) return AVERROR_INVALIDDATA; + if (c->fc->nb_streams < 1) + return 0; + st = c->fc->streams[c->fc->nb_streams - 1]; + sc = st->priv_data; + ret = avio_read(pb, uuid, sizeof(uuid)); if (ret < 0) { return ret; @@ -4585,7 +4818,14 @@ static int mov_read_uuid(MOVContext *c, AVIOContext *pb, MOVAtom atom) av_dict_set(&c->fc->metadata, "xmp", buffer, 0); } av_free(buffer); - } + } else if (!memcmp(uuid, uuid_spherical, sizeof(uuid))) { + size_t len = atom.size - sizeof(uuid); + ret = mov_parse_uuid_spherical(sc, pb, len); + if (ret < 0) + return ret; + if (!sc->spherical) + av_log(c->fc, AV_LOG_WARNING, "Invalid spherical metadata found\n"); } + return 0; } @@ -4935,6 +5175,8 @@ static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('f','r','m','a'), mov_read_frma }, { MKTAG('s','e','n','c'), mov_read_senc }, { MKTAG('s','a','i','z'), mov_read_saiz }, +{ MKTAG('s','t','3','d'), mov_read_st3d }, /* stereoscopic 3D video box */ +{ MKTAG('s','v','3','d'), mov_read_sv3d }, /* spherical video box */ { 0, NULL } }; @@ -5355,6 +5597,9 @@ static int mov_read_close(AVFormatContext *s) av_freep(&sc->cenc.auxiliary_info); av_freep(&sc->cenc.auxiliary_info_sizes); av_aes_ctr_free(sc->cenc.aes_ctr); + + av_freep(&sc->stereo3d); + av_freep(&sc->spherical); } if (mov->dv_demux) { @@ -5682,6 +5927,40 @@ static int mov_read_header(AVFormatContext *s) sd->data = (uint8_t*)sc->display_matrix; sc->display_matrix = NULL; } + if (sc->stereo3d) { + AVPacketSideData *sd, *tmp; + + tmp = av_realloc_array(st->side_data, + st->nb_side_data + 1, sizeof(*tmp)); + if (!tmp) + return AVERROR(ENOMEM); + + st->side_data = tmp; + st->nb_side_data++; + + sd = &st->side_data[st->nb_side_data - 1]; + sd->type = AV_PKT_DATA_STEREO3D; + sd->size = sizeof(*sc->stereo3d); + sd->data = (uint8_t *)sc->stereo3d; + sc->stereo3d = NULL; + } + if (sc->spherical) { + AVPacketSideData *sd, *tmp; + + tmp = av_realloc_array(st->side_data, + st->nb_side_data + 1, sizeof(*tmp)); + if (!tmp) + return AVERROR(ENOMEM); + + st->side_data = tmp; + st->nb_side_data++; + + sd = &st->side_data[st->nb_side_data - 1]; + sd->type = AV_PKT_DATA_SPHERICAL; + sd->size = sc->spherical_size; + sd->data = (uint8_t *)sc->spherical; + sc->spherical = NULL; + } break; } }