diff mbox

[FFmpeg-devel,1/1] This change adds an encoder for Camera metadata motion. This is a type of sensor data associated with video, such as GPS, acceleration, gyro, and camera orientation. It does not encode video itself, but rather, this metadata.

Message ID 20170707003639.27739-2-louiso@louiso0.mtv.corp.google.com
State New
Headers show

Commit Message

Louis O'Bryan July 7, 2017, 12:36 a.m. UTC
From: Louis O'Bryan <louiso@google.com>

Signed-off-by: Louis O'Bryan <louiso@google.com>
---
 Changelog               |   1 +
 doc/general.texi        |   2 +
 libavcodec/Makefile     |   1 +
 libavcodec/allcodecs.c  |   3 +
 libavcodec/avcodec.h    |   1 +
 libavcodec/cammenc.c    | 299 ++++++++++++++++++++++++++++++++++++++++++++++++
 libavcodec/codec_desc.c |   6 +
 libavcodec/version.h    |   4 +-
 libavformat/isom.c      |   6 +
 libavformat/movenc.c    | 200 +++++++++++++++++---------------
 10 files changed, 431 insertions(+), 92 deletions(-)
 create mode 100644 libavcodec/cammenc.c

Comments

Louis O'Bryan July 7, 2017, 12:39 a.m. UTC | #1
FYI - sorry if these emails are duplicates.

On Thu, Jul 6, 2017 at 5:36 PM, Louis O'Bryan <lboextdev@gmail.com> wrote:

> From: Louis O'Bryan <louiso@google.com>
>
> Signed-off-by: Louis O'Bryan <louiso@google.com>
> ---
>  Changelog               |   1 +
>  doc/general.texi        |   2 +
>  libavcodec/Makefile     |   1 +
>  libavcodec/allcodecs.c  |   3 +
>  libavcodec/avcodec.h    |   1 +
>  libavcodec/cammenc.c    | 299 ++++++++++++++++++++++++++++++
> ++++++++++++++++++
>  libavcodec/codec_desc.c |   6 +
>  libavcodec/version.h    |   4 +-
>  libavformat/isom.c      |   6 +
>  libavformat/movenc.c    | 200 +++++++++++++++++---------------
>  10 files changed, 431 insertions(+), 92 deletions(-)
>  create mode 100644 libavcodec/cammenc.c
>
> diff --git a/Changelog b/Changelog
> index a8726c6736..5f98385b53 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -2,6 +2,7 @@ Entries are sorted chronologically from oldest to youngest
> within each release,
>  releases are sorted from youngest to oldest.
>
>  version <next>:
> +- Camera metadata motion encoder
>  - deflicker video filter
>  - doubleweave video filter
>  - lumakey video filter
> diff --git a/doc/general.texi b/doc/general.texi
> index 8f582d586f..06996c81e8 100644
> --- a/doc/general.texi
> +++ b/doc/general.texi
> @@ -912,6 +912,8 @@ following image formats are supported:
>      @tab part of LCL, encoder experimental
>  @item Zip Motion Blocks Video  @tab   X @tab  X
>      @tab Encoder works only in PAL8.
> +@item Camera metadata motion    @tab X  @tab
> +    @tab Encoder for camera sensor data.
>  @end multitable
>
>  @code{X} means that encoding (resp. decoding) is supported.
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index b440a00746..306cc793ee 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -680,6 +680,7 @@ OBJS-$(CONFIG_ZLIB_DECODER)            += lcldec.o
>  OBJS-$(CONFIG_ZLIB_ENCODER)            += lclenc.o
>  OBJS-$(CONFIG_ZMBV_DECODER)            += zmbv.o
>  OBJS-$(CONFIG_ZMBV_ENCODER)            += zmbvenc.o
> +OBJS-$(CONFIG_CAMERA_MOTION_METADATA_ENCODER) += cammenc.o
>
>  # (AD)PCM decoders/encoders
>  OBJS-$(CONFIG_PCM_ALAW_DECODER)           += pcm.o
> diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> index 0243f47358..eff51f4042 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -651,6 +651,9 @@ static void register_all(void)
>      REGISTER_DECODER(XBIN,              xbin);
>      REGISTER_DECODER(IDF,               idf);
>
> +    /* data */
> +    REGISTER_ENCODER(CAMERA_MOTION_METADATA, camera_motion_metadata);
> +
>      /* external libraries, that shouldn't be used by default if one of the
>       * above is available */
>      REGISTER_ENCDEC (LIBOPENH264,       libopenh264);
> diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
> index b697afa0ae..622383f453 100644
> --- a/libavcodec/avcodec.h
> +++ b/libavcodec/avcodec.h
> @@ -681,6 +681,7 @@ enum AVCodecID {
>      AV_CODEC_ID_DVD_NAV,
>      AV_CODEC_ID_TIMED_ID3,
>      AV_CODEC_ID_BIN_DATA,
> +    AV_CODEC_ID_CAMERA_MOTION_METADATA,
>
>
>      AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like
> AV_CODEC_ID_NONE) but lavf should attempt to identify it
> diff --git a/libavcodec/cammenc.c b/libavcodec/cammenc.c
> new file mode 100644
> index 0000000000..d7592ab69d
> --- /dev/null
> +++ b/libavcodec/cammenc.c
> @@ -0,0 +1,299 @@
> +/*
> + * Reference implementation for the CAMM Metadata encoder.
> + * Encodes sensor data for 360-degree cameras such as
> + * GPS, gyro, and acceleration. This is stored in a track separate from
> video
> + * and audio.
> + *
> + * Copyright (c) 2017 Louis O'Bryan
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA
> + */
> +
> +/**
> + * CAMM Metadata encoder
> + * @author Louis O'Bryan
> + */
> +
> +#include <math.h>
> +#include <float.h>
> +#include "avcodec.h"
> +#include "internal.h"
> +#include "bytestream.h"
> +#include "libavutil/common.h"
> +#include "libavutil/opt.h"
> +
> +#define DUMMY_ENCODER_SIZE 1
> +
> +typedef struct CammContext {
> +    AVCodecContext *avctx;
> +} CammContext;
> +
> +// Sizes of each type of metadata.
> +static int metadata_type_sizes[] = {
> +  3 * sizeof(float),
> +  2 * sizeof(uint64_t),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(double) + sizeof(uint32_t) + 7 * sizeof(float),
> +  3 * sizeof(float)
> +};
> +
> +static int min_packet_size = sizeof(uint16_t) * 2;
> +
> +/**
> + * Validates that the latitude has a valid value. Returns 1 if ok, else 0.
> + */
> +static int validate_latitude(AVCodecContext *avctx, double latitude) {
> +    if (latitude < -M_PI / 4 || latitude > M_PI / 4) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Latitude %f is not in bounds (%f, %f)\n",
> +               latitude, -M_PI / 4, M_PI / 4);
> +        return 0;
> +    }
> +    return 1;
> +}
> +
> +/**
> + * Validates that the longitude has a valid value. Returns 1 if ok, else
> 0.
> + */
> +static int validate_longitude(AVCodecContext *avctx, double longitude) {
> +    if (longitude < -M_PI / 2 || longitude > M_PI / 2) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "longitude %f is not in bounds (%f, %f)\n",
> +               longitude, -M_PI / 2, M_PI / 2);
> +        return 0;
> +    }
> +    return 1;
> +}
> +
> +static void log_float_values(AVCodecContext *avctx, const char *name,
> float *data) {
> +    int i;
> +    float value;
> +    for (i = 0; i < 3; ++i) {
> +        value = data[i];
> +        av_log(avctx, AV_LOG_DEBUG, "%s[%d] = %f\n", name, i, value);
> +    }
> +}
> +
> +/**
> + * Validates that the incoming data is correctly formatted and returns
> + * 1 if it is, 0 otherwise.
> + */
> +static int validate_data(AVCodecContext *avctx, const AVFrame *data) {
> +    uint16_t packet_type;
> +    int expected_packet_size;
> +    uint64_t pixel_exposure_time;
> +    uint64_t rolling_shutter_skew_time;
> +    uint32_t *camm_data;
> +    double time_gps_epoch;
> +    uint32_t gps_fix_type;
> +    double latitude;
> +    double longitude;
> +    float altitude;
> +
> +    if (data->pkt_size < min_packet_size) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "CAMM input data with size %d is too small\n",
> data->pkt_size);
> +        return 0;
> +    }
> +
> +    packet_type = ((uint16_t*)(*data->extended_data))[1];
> +    if (packet_type > sizeof(metadata_type_sizes) / sizeof(int)) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Packet type %d is not recognized\n", packet_type);
> +        return 0;
> +    }
> +
> +    expected_packet_size = metadata_type_sizes[packet_type];
> +    if (expected_packet_size != data->pkt_size) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Packet size %d does not match expected size %d for type
> %d\n",
> +               data->pkt_size, expected_packet_size, packet_type);
> +        return 0;
> +    }
> +
> +    // The actual sensor data starts after the reserved slot and packet
> type.
> +    camm_data = ((uint32_t*)(*data->extended_data)) + 1;
> +    switch (packet_type) {
> +        // float angle_axis[3]
> +        case 0:
> +            log_float_values(avctx, "angle_axis", (float*)camm_data);
> +            break;
> +        // int32 pixel_exposure_time
> +        // int32 rolling_shutter_skew_time
> +        case 1:
> +            pixel_exposure_time = ((uint32_t*)camm_data)[0];
> +            av_log(avctx, AV_LOG_DEBUG,
> +                   "pixel_exposure_time = %lu\n", pixel_exposure_time);
> +            rolling_shutter_skew_time = ((uint32_t*)camm_data)[1];
> +            av_log(avctx, AV_LOG_DEBUG, "rolling_shutter_skew_time =
> %lu\n",
> +                   rolling_shutter_skew_time);
> +            break;
> +        // float gyro[3]
> +        case 2:
> +            log_float_values(avctx, "gyro", (float*)camm_data);
> +            break;
> +        case 3:
> +            log_float_values(avctx, "acceleration", (float*)camm_data);
> +            break;
> +        // float position[3]
> +        case 4:
> +            log_float_values(avctx, "position", (float*)camm_data);
> +            break;
> +        // float latitude
> +        // float longitude
> +        // float altitude
> +        case 5:
> +            if (!validate_latitude(avctx, ((float*)camm_data)[0])) return
> 0;
> +            if (!validate_longitude(avctx, ((float*)camm_data)[1]))
> return 0;
> +
> +            av_log(avctx, AV_LOG_DEBUG, "latitude = %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            av_log(avctx, AV_LOG_DEBUG, "longitude = %.6f\n",
> +                   ((float*)camm_data)[1]);
> +            av_log(avctx, AV_LOG_DEBUG, "altitude = %.6f\n",
> +                   ((float*)camm_data)[2]);
> +            break;
> +        case 6:
> +            time_gps_epoch = ((double*)camm_data)[0];
> +            if (time_gps_epoch < 0) {
> +                av_log(avctx, AV_LOG_ERROR,
> +                       "Time gps epoch %.6f is less than 0.\n",
> +                       time_gps_epoch);
> +                return 0;
> +            }
> +            av_log(avctx, AV_LOG_DEBUG, "time_gps_epoch = %.6f\n",
> +                   time_gps_epoch);
> +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> +
> +            gps_fix_type = ((uint32_t*)camm_data)[0];
> +            if (gps_fix_type != 0 && gps_fix_type != 1 && gps_fix_type !=
> 2) {
> +                av_log(avctx, AV_LOG_ERROR,
> +                       "GPS fix type %d is not valid. Should be 0, 1, or
> 2\n",
> +                       gps_fix_type);
> +                return 0;
> +            }
> +            av_log(avctx, AV_LOG_DEBUG, "gps_fix_type = %d\n",
> gps_fix_type);
> +            camm_data = (uint32_t*) (((int32_t*)camm_data) + 1);
> +
> +            latitude = ((double*)camm_data)[0];
> +            if (!validate_latitude(avctx, latitude)) return 0;
> +            av_log(avctx, AV_LOG_DEBUG, "latitude = %.6f\n", latitude);
> +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> +
> +            longitude = ((double*)camm_data)[0];
> +            if (!validate_longitude(avctx, longitude)) return 0;
> +            av_log(avctx, AV_LOG_DEBUG, "longitude = %.6f\n", latitude);
> +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> +
> +            altitude = ((float*)camm_data)[0];
> +            av_log(avctx, AV_LOG_DEBUG, "altitude = %.6f\n", altitude);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "horizontal accuracy = %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "vertical accuracy %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "vertical east %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "vertical north %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "vertical up %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "speed accuracy %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +            break;
> +        // float magnetic_field[3]
> +        case 7:
> +            log_float_values(avctx, "magnetic_field", (float*)camm_data);
> +            break;
> +    }
> +
> +    return 1;
> +}
> +
> +/**
> + * Encode camera motion metadata. Essentially, this encoder validates the
> + * input data and then just copies the data to an output AVPacket.
> + *  @param avctx          The AVCodecContext context.
> + *  @param packet         The output packet to write the encoded data.
> + *  @param data           The input camera sensor data.
> + *  @param got_packet     1 if a non-empty packet was returned, 0
> otherwise.
> + */
> +static int encode_data(AVCodecContext *avctx, AVPacket *packet,
> +                        const AVFrame *data, int *got_packet)
> +{
> +    int ret;
> +
> +    if (!validate_data(avctx, data)) {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    // Make sure there is enough size allocated in packet->data.
> +    if ((ret = ff_alloc_packet2(avctx, packet, data->pkt_size, 0)) < 0) {
> +        av_log(avctx, AV_LOG_ERROR, "Can't allocate %d bytes in packet\n",
> +               data->pkt_size);
> +        return ret;
> +    }
> +
> +    // Copy necessary fields to the AVPacket.
> +    packet->pts = data->pts;
> +    packet->duration = data->pkt_duration;
> +    memcpy(packet->data, *(data->extended_data), data->pkt_size);
> +
> +    // Indicate that a packet was created.
> +    *got_packet = 1;
> +    return 0;
> +}
> +
> +/**
> + * Initializes the codec.
> + *
> + * @param avctx     The AVCodecContext context.
> + */
> +static av_cold int encode_init(AVCodecContext *avctx) {
> +    // Use dummy values for the height and width.
> +    avctx->width = DUMMY_ENCODER_SIZE;
> +    avctx->height = DUMMY_ENCODER_SIZE;
> +    avctx->max_pixels = DUMMY_ENCODER_SIZE;
> +
> +    return 0;
> +}
> +
> +// Define the encoder that ffmpeg will use for CAMM data.
> +AVCodec ff_camera_motion_metadata_encoder = {
> +    .name           = "camm",
> +    .long_name      = NULL_IF_CONFIG_SMALL("camera motion metadata"),
> +    .type           = AVMEDIA_TYPE_DATA,
> +    .id             = AV_CODEC_ID_CAMERA_MOTION_METADATA,
> +    .priv_data_size = sizeof(CammContext),
> +    .encode2        = encode_data,
> +    .init           = encode_init,
> +};
> diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
> index cf1246e431..014c1cd6b3 100644
> --- a/libavcodec/codec_desc.c
> +++ b/libavcodec/codec_desc.c
> @@ -3100,6 +3100,12 @@ static const AVCodecDescriptor codec_descriptors[]
> = {
>          .name      = "scte_35",
>          .long_name = NULL_IF_CONFIG_SMALL("SCTE 35 Message Queue"),
>      },
> +    {
> +        .id        = AV_CODEC_ID_CAMERA_MOTION_METADATA,
> +        .type      = AVMEDIA_TYPE_DATA,
> +        .name      = "camm",
> +        .long_name = NULL_IF_CONFIG_SMALL("camera motion metadata"),
> +    },
>
>      /* deprecated codec ids */
>  };
> diff --git a/libavcodec/version.h b/libavcodec/version.h
> index 3c5fea9327..5b99785a72 100644
> --- a/libavcodec/version.h
> +++ b/libavcodec/version.h
> @@ -28,8 +28,8 @@
>  #include "libavutil/version.h"
>
>  #define LIBAVCODEC_VERSION_MAJOR  57
> -#define LIBAVCODEC_VERSION_MINOR 100
> -#define LIBAVCODEC_VERSION_MICRO 103
> +#define LIBAVCODEC_VERSION_MINOR 101
> +#define LIBAVCODEC_VERSION_MICRO 100
>
>  #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR,
> \
>                                                 LIBAVCODEC_VERSION_MINOR, \
> diff --git a/libavformat/isom.c b/libavformat/isom.c
> index 3a9b3baf96..ca5107bf7f 100644
> --- a/libavformat/isom.c
> +++ b/libavformat/isom.c
> @@ -67,6 +67,7 @@ const AVCodecTag ff_mp4_obj_type[] = {
>      { AV_CODEC_ID_VORBIS      , 0xDD }, /* nonstandard, gpac uses it */
>      { AV_CODEC_ID_DVD_SUBTITLE, 0xE0 }, /* nonstandard, see
> unsupported-embedded-subs-2.mp4 */
>      { AV_CODEC_ID_QCELP       , 0xE1 },
> +    { AV_CODEC_ID_CAMERA_MOTION_METADATA, 0xE2 },
>      { AV_CODEC_ID_MPEG4SYSTEMS, 0x01 },
>      { AV_CODEC_ID_MPEG4SYSTEMS, 0x02 },
>      { AV_CODEC_ID_NONE        ,    0 },
> @@ -368,6 +369,11 @@ const AVCodecTag ff_codec_movsubtitle_tags[] = {
>      { AV_CODEC_ID_NONE, 0 },
>  };
>
> +const AVCodecTag ff_codec_movdata_tags[] = {
> +    { AV_CODEC_ID_CAMERA_MOTION_METADATA, MKTAG('c','a','m','m') },
> +};
> +
> +
>  /* map numeric codes from mdhd atom to ISO 639 */
>  /* cf. QTFileFormat.pdf p253, qtff.pdf p205 */
>  /* http://developer.apple.com/documentation/mac/Text/Text-368.html */
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index 88f2f2c819..49528f8635 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -35,8 +35,6 @@
>  #include "libavcodec/dnxhddata.h"
>  #include "libavcodec/flac.h"
>  #include "libavcodec/get_bits.h"
> -
> -#include "libavcodec/internal.h"
>  #include "libavcodec/put_bits.h"
>  #include "libavcodec/vc1_common.h"
>  #include "libavcodec/raw.h"
> @@ -1126,10 +1124,7 @@ static int mov_write_hvcc_tag(AVIOContext *pb,
> MOVTrack *track)
>
>      avio_wb32(pb, 0);
>      ffio_wfourcc(pb, "hvcC");
> -    if (track->tag == MKTAG('h','v','c','1'))
> -        ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 1);
> -    else
> -        ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
> +    ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
>      return update_size(pb, pos);
>  }
>
> @@ -1230,6 +1225,60 @@ static int mov_write_dpxe_tag(AVIOContext *pb,
> MOVTrack *track)
>      return 0;
>  }
>
> +static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track)
> +{
> +    int tag = track->par->codec_tag;
> +
> +    if (!ff_codec_get_tag(ff_mp4_obj_type, track->par->codec_id))
> +        return 0;
> +
> +    if      (track->par->codec_id == AV_CODEC_ID_H264)      tag =
> MKTAG('a','v','c','1');
> +    else if (track->par->codec_id == AV_CODEC_ID_HEVC)      tag =
> MKTAG('h','e','v','1');
> +    else if (track->par->codec_id == AV_CODEC_ID_VP9)       tag =
> MKTAG('v','p','0','9');
> +    else if (track->par->codec_id == AV_CODEC_ID_AC3)       tag =
> MKTAG('a','c','-','3');
> +    else if (track->par->codec_id == AV_CODEC_ID_EAC3)      tag =
> MKTAG('e','c','-','3');
> +    else if (track->par->codec_id == AV_CODEC_ID_DIRAC)     tag =
> MKTAG('d','r','a','c');
> +    else if (track->par->codec_id == AV_CODEC_ID_MOV_TEXT)  tag =
> MKTAG('t','x','3','g');
> +    else if (track->par->codec_id == AV_CODEC_ID_VC1)       tag =
> MKTAG('v','c','-','1');
> +    else if (track->par->codec_id == AV_CODEC_ID_FLAC)      tag =
> MKTAG('f','L','a','C');
> +    else if (track->par->codec_id == AV_CODEC_ID_OPUS)      tag =
> MKTAG('O','p','u','s');
> +    else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO)  tag =
> MKTAG('m','p','4','v');
> +    else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO)  tag =
> MKTAG('m','p','4','a');
> +    else if (track->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE)  tag =
> MKTAG('m','p','4','s');
> +
> +    return tag;
> +}
> +
> +static const AVCodecTag codec_ipod_tags[] = {
> +    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> +    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> +    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> +    { AV_CODEC_ID_ALAC,     MKTAG('a','l','a','c') },
> +    { AV_CODEC_ID_AC3,      MKTAG('a','c','-','3') },
> +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') },
> +    { AV_CODEC_ID_NONE, 0 },
> +};
> +
> +static int ipod_get_codec_tag(AVFormatContext *s, MOVTrack *track)
> +{
> +    int tag = track->par->codec_tag;
> +
> +    // keep original tag for subs, ipod supports both formats
> +    if (!(track->par->codec_type == AVMEDIA_TYPE_SUBTITLE &&
> +          (tag == MKTAG('t', 'x', '3', 'g') ||
> +           tag == MKTAG('t', 'e', 'x', 't'))))
> +        tag = ff_codec_get_tag(codec_ipod_tags, track->par->codec_id);
> +
> +    if (!av_match_ext(s->filename, "m4a") &&
> +        !av_match_ext(s->filename, "m4b") &&
> +        !av_match_ext(s->filename, "m4v"))
> +        av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a, .m4v
> nor  .m4b "
> +               "Quicktime/Ipod might not play the file\n");
> +
> +    return tag;
> +}
> +
>  static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track)
>  {
>      int tag;
> @@ -1506,25 +1555,42 @@ static int mov_get_codec_tag(AVFormatContext *s,
> MOVTrack *track)
>      return tag;
>  }
>
> +static const AVCodecTag codec_3gp_tags[] = {
> +    { AV_CODEC_ID_H263,     MKTAG('s','2','6','3') },
> +    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> +    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> +    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> +    { AV_CODEC_ID_AMR_NB,   MKTAG('s','a','m','r') },
> +    { AV_CODEC_ID_AMR_WB,   MKTAG('s','a','w','b') },
> +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> +    { AV_CODEC_ID_NONE, 0 },
> +};
> +
> +static const AVCodecTag codec_f4v_tags[] = { // XXX: add GIF/PNG/JPEG?
> +    { AV_CODEC_ID_MP3,    MKTAG('.','m','p','3') },
> +    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
> +    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
> +    { AV_CODEC_ID_VP6A,   MKTAG('V','P','6','A') },
> +    { AV_CODEC_ID_VP6F,   MKTAG('V','P','6','F') },
> +    { AV_CODEC_ID_NONE, 0 },
> +};
> +
>  static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track)
>  {
>      int tag;
>
>      if (track->mode == MODE_MP4 || track->mode == MODE_PSP)
> -        tag = track->par->codec_tag;
> -    else if (track->mode == MODE_ISM)
> -        tag = track->par->codec_tag;
> -    else if (track->mode == MODE_IPOD) {
> -        if (!av_match_ext(s->filename, "m4a") &&
> -            !av_match_ext(s->filename, "m4v") &&
> -            !av_match_ext(s->filename, "m4b"))
> -            av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor
> .m4v "
> -                   "Quicktime/Ipod might not play the file\n");
> -        tag = track->par->codec_tag;
> -    } else if (track->mode & MODE_3GP)
> -        tag = track->par->codec_tag;
> +        tag = mp4_get_codec_tag(s, track);
> +    else if (track->mode == MODE_ISM) {
> +        tag = mp4_get_codec_tag(s, track);
> +        if (!tag && track->par->codec_id == AV_CODEC_ID_WMAPRO)
> +            tag = MKTAG('w', 'm', 'a', ' ');
> +    } else if (track->mode == MODE_IPOD)
> +        tag = ipod_get_codec_tag(s, track);
> +    else if (track->mode & MODE_3GP)
> +        tag = ff_codec_get_tag(codec_3gp_tags, track->par->codec_id);
>      else if (track->mode == MODE_F4V)
> -        tag = track->par->codec_tag;
> +        tag = ff_codec_get_tag(codec_f4v_tags, track->par->codec_id);
>      else
>          tag = mov_get_codec_tag(s, track);
>
> @@ -2060,6 +2126,19 @@ static int mov_write_tmcd_tag(AVIOContext *pb,
> MOVTrack *track)
>      return update_size(pb, pos);
>  }
>
> +static int mov_write_camm_tag(AVIOContext *pb) {
> +    int64_t size_update;
> +    int64_t pos = avio_tell(pb);
> +    avio_wb32(pb, 0); /* size */
> +    ffio_wfourcc(pb, "camm");
> +    avio_wb32(pb, 0); /* Reserved */
> +    avio_wb16(pb, 0); /* Reserved */
> +    avio_wb16(pb, 1); /* Data-reference index */
> +
> +    size_update = update_size(pb, pos);
> +    return size_update;
> +}
> +
>  static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb,
> MOVMuxContext *mov, MOVTrack *track)
>  {
>      int64_t pos = avio_tell(pb);
> @@ -2077,6 +2156,10 @@ static int mov_write_stsd_tag(AVFormatContext *s,
> AVIOContext *pb, MOVMuxContext
>          mov_write_rtp_tag(pb, track);
>      else if (track->par->codec_tag == MKTAG('t','m','c','d'))
>          mov_write_tmcd_tag(pb, track);
> +    else if (track->par->codec_tag
> +             == ff_codec_get_tag(ff_mp4_obj_type,
> +                                 AV_CODEC_ID_CAMERA_MOTION_METADATA))
> +        mov_write_camm_tag(pb);
>      return update_size(pb, pos);
>  }
>
> @@ -2443,6 +2526,11 @@ static int mov_write_hdlr_tag(AVFormatContext *s,
> AVIOContext *pb, MOVTrack *tra
>          } else if (track->par->codec_tag == MKTAG('t','m','c','d')) {
>              hdlr_type = "tmcd";
>              descr = "TimeCodeHandler";
> +        } else if (track->par->codec_tag
> +                       == ff_codec_get_tag(ff_mp4_obj_type,
> +                            AV_CODEC_ID_CAMERA_MOTION_METADATA)) {
> +            hdlr_type = "camm";
> +            descr = "CameraMetadataMotionHandler";
>          } else {
>              av_log(s, AV_LOG_WARNING,
>                     "Unknown hldr_type for %s, writing dummy values\n",
> @@ -6422,73 +6510,6 @@ static int mov_check_bitstream(struct
> AVFormatContext *s, const AVPacket *pkt)
>      return ret;
>  }
>
> -static const AVCodecTag codec_3gp_tags[] = {
> -    { AV_CODEC_ID_H263,     MKTAG('s','2','6','3') },
> -    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> -    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> -    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> -    { AV_CODEC_ID_AMR_NB,   MKTAG('s','a','m','r') },
> -    { AV_CODEC_ID_AMR_WB,   MKTAG('s','a','w','b') },
> -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> -    { AV_CODEC_ID_NONE, 0 },
> -};
> -
> -const AVCodecTag codec_mp4_tags[] = {
> -    { AV_CODEC_ID_MPEG4       , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_H264        , MKTAG('a', 'v', 'c', '1') },
> -    { AV_CODEC_ID_HEVC        , MKTAG('h', 'e', 'v', '1') },
> -    { AV_CODEC_ID_HEVC        , MKTAG('h', 'v', 'c', '1') },
> -    { AV_CODEC_ID_MPEG2VIDEO  , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_MPEG1VIDEO  , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_MJPEG       , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_PNG         , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_JPEG2000    , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_VC1         , MKTAG('v', 'c', '-', '1') },
> -    { AV_CODEC_ID_DIRAC       , MKTAG('d', 'r', 'a', 'c') },
> -    { AV_CODEC_ID_TSCC2       , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_VP9         , MKTAG('v', 'p', '0', '9') },
> -    { AV_CODEC_ID_AAC         , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_MP4ALS      , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_MP3         , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_MP2         , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_AC3         , MKTAG('a', 'c', '-', '3') },
> -    { AV_CODEC_ID_EAC3        , MKTAG('e', 'c', '-', '3') },
> -    { AV_CODEC_ID_DTS         , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_FLAC        , MKTAG('f', 'L', 'a', 'C') },
> -    { AV_CODEC_ID_OPUS        , MKTAG('O', 'p', 'u', 's') },
> -    { AV_CODEC_ID_VORBIS      , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_QCELP       , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_EVRC        , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_DVD_SUBTITLE, MKTAG('m', 'p', '4', 's') },
> -    { AV_CODEC_ID_MOV_TEXT    , MKTAG('t', 'x', '3', 'g') },
> -    { AV_CODEC_ID_NONE        ,    0 },
> -};
> -
> -const AVCodecTag codec_ism_tags[] = {
> -    { AV_CODEC_ID_WMAPRO      , MKTAG('w', 'm', 'a', ' ') },
> -    { AV_CODEC_ID_NONE        ,    0 },
> -};
> -
> -static const AVCodecTag codec_ipod_tags[] = {
> -    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> -    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> -    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> -    { AV_CODEC_ID_ALAC,     MKTAG('a','l','a','c') },
> -    { AV_CODEC_ID_AC3,      MKTAG('a','c','-','3') },
> -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') },
> -    { AV_CODEC_ID_NONE, 0 },
> -};
> -
> -static const AVCodecTag codec_f4v_tags[] = {
> -    { AV_CODEC_ID_MP3,    MKTAG('.','m','p','3') },
> -    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
> -    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
> -    { AV_CODEC_ID_VP6A,   MKTAG('V','P','6','A') },
> -    { AV_CODEC_ID_VP6F,   MKTAG('V','P','6','F') },
> -    { AV_CODEC_ID_NONE, 0 },
> -};
> -
>  #if CONFIG_MOV_MUXER
>  MOV_CLASS(mov)
>  AVOutputFormat ff_mov_muxer = {
> @@ -6549,7 +6570,7 @@ AVOutputFormat ff_mp4_muxer = {
>      .write_trailer     = mov_write_trailer,
>      .deinit            = mov_free,
>      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH |
> AVFMT_TS_NEGATIVE,
> -    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags, 0
> },
> +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0
> },
>      .check_bitstream   = mov_check_bitstream,
>      .priv_class        = &mp4_muxer_class,
>  };
> @@ -6570,7 +6591,7 @@ AVOutputFormat ff_psp_muxer = {
>      .write_trailer     = mov_write_trailer,
>      .deinit            = mov_free,
>      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH |
> AVFMT_TS_NEGATIVE,
> -    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags, 0
> },
> +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0
> },
>      .check_bitstream   = mov_check_bitstream,
>      .priv_class        = &psp_muxer_class,
>  };
> @@ -6632,8 +6653,7 @@ AVOutputFormat ff_ismv_muxer = {
>      .write_trailer     = mov_write_trailer,
>      .deinit            = mov_free,
>      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH |
> AVFMT_TS_NEGATIVE,
> -    .codec_tag         = (const AVCodecTag* const []){
> -        codec_mp4_tags, codec_ism_tags, 0 },
> +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0
> },
>      .check_bitstream   = mov_check_bitstream,
>      .priv_class        = &ismv_muxer_class,
>  };
> --
> 2.13.2.725.g09c95d1e9-goog
>
>
Moritz Barsnick July 7, 2017, 10:14 a.m. UTC | #2
Just some small observations... Even if you postpone this patch,
perhaps it can help improve the next one.

On Thu, Jul 06, 2017 at 17:36:39 -0700, Louis O'Bryan wrote:

> @@ -2,6 +2,7 @@ Entries are sorted chronologically from oldest to youngest within each release,
>  releases are sorted from youngest to oldest.
>  
>  version <next>:
> +- Camera metadata motion encoder
>  - deflicker video filter

Did you read that sentence above? ;-) New entries at the bottom of the
"version <next>" section please.

>  @item Zip Motion Blocks Video  @tab   X @tab  X
>      @tab Encoder works only in PAL8.
> +@item Camera metadata motion    @tab X  @tab
> +    @tab Encoder for camera sensor data.

This list is supposed to be alphabetical.

>  OBJS-$(CONFIG_ZLIB_ENCODER)            += lclenc.o
>  OBJS-$(CONFIG_ZMBV_DECODER)            += zmbv.o
>  OBJS-$(CONFIG_ZMBV_ENCODER)            += zmbvenc.o
> +OBJS-$(CONFIG_CAMERA_MOTION_METADATA_ENCODER) += cammenc.o

This also needs to be in alphabetical order.

> + * Reference implementation for the CAMM Metadata encoder.
> + * Encodes sensor data for 360-degree cameras such as
> + * GPS, gyro, and acceleration. This is stored in a track separate from video
> + * and audio.

I believe you're just supposed to have a short entry here, the more
detailed stuff goes into the section below the GPL boilerplate.

> +// Sizes of each type of metadata.
> +static int metadata_type_sizes[] = {
> +  3 * sizeof(float),
> +  2 * sizeof(uint64_t),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(double) + sizeof(uint32_t) + 7 * sizeof(float),
> +  3 * sizeof(float)
> +};

Doesn't this make the sizes platform dependant? (Unless all platforms
have standardized floats anyway, then I'm mistaken. I just don't know.)

> +            av_log(avctx, AV_LOG_DEBUG,
> +                   "pixel_exposure_time = %lu\n", pixel_exposure_time);

pixel_exposure_time is a uint64_t, so '%lu' is incorrect, it's
'%"PRIu64"'. (Elsewhere as well.) The <stdint.h> types have theitr own
format identifiers.

> +            av_log(avctx, AV_LOG_DEBUG, "rolling_shutter_skew_time = %lu\n",
> +                   rolling_shutter_skew_time);

Ditto.

> +            if (gps_fix_type != 0 && gps_fix_type != 1 && gps_fix_type != 2) {

You could do "gps_fix_type > 2", but since it's sort of an enum, I
guess this is fine.


> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c

This does NOT belong into your patch! (Or only parts of it.) You copied
some modified code over a recent git checkout, thereby apparently
reverting some unrelated recent commits. I recommend you checkout/pull,
modify only you stuff, and then commit locally. That keeps your changes
constrained, even if the code changes upstream.

Please re-apply your changes to a recent checkout.

Moritz
wm4 July 7, 2017, 10:45 a.m. UTC | #3
On Thu,  6 Jul 2017 17:36:39 -0700
"Louis O'Bryan" <lboextdev@gmail.com> wrote:

> From: Louis O'Bryan <louiso@google.com>
> 
> Signed-off-by: Louis O'Bryan <louiso@google.com>
> ---
>  Changelog               |   1 +
>  doc/general.texi        |   2 +
>  libavcodec/Makefile     |   1 +
>  libavcodec/allcodecs.c  |   3 +
>  libavcodec/avcodec.h    |   1 +
>  libavcodec/cammenc.c    | 299 ++++++++++++++++++++++++++++++++++++++++++++++++
>  libavcodec/codec_desc.c |   6 +
>  libavcodec/version.h    |   4 +-
>  libavformat/isom.c      |   6 +
>  libavformat/movenc.c    | 200 +++++++++++++++++---------------
>  10 files changed, 431 insertions(+), 92 deletions(-)
>  create mode 100644 libavcodec/cammenc.c
> 
> diff --git a/Changelog b/Changelog
> index a8726c6736..5f98385b53 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -2,6 +2,7 @@ Entries are sorted chronologically from oldest to youngest within each release,
>  releases are sorted from youngest to oldest.
>  
>  version <next>:
> +- Camera metadata motion encoder
>  - deflicker video filter
>  - doubleweave video filter
>  - lumakey video filter
> diff --git a/doc/general.texi b/doc/general.texi
> index 8f582d586f..06996c81e8 100644
> --- a/doc/general.texi
> +++ b/doc/general.texi
> @@ -912,6 +912,8 @@ following image formats are supported:
>      @tab part of LCL, encoder experimental
>  @item Zip Motion Blocks Video  @tab   X @tab  X
>      @tab Encoder works only in PAL8.
> +@item Camera metadata motion    @tab X  @tab
> +    @tab Encoder for camera sensor data.
>  @end multitable
>  
>  @code{X} means that encoding (resp. decoding) is supported.
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index b440a00746..306cc793ee 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -680,6 +680,7 @@ OBJS-$(CONFIG_ZLIB_DECODER)            += lcldec.o
>  OBJS-$(CONFIG_ZLIB_ENCODER)            += lclenc.o
>  OBJS-$(CONFIG_ZMBV_DECODER)            += zmbv.o
>  OBJS-$(CONFIG_ZMBV_ENCODER)            += zmbvenc.o
> +OBJS-$(CONFIG_CAMERA_MOTION_METADATA_ENCODER) += cammenc.o
>  
>  # (AD)PCM decoders/encoders
>  OBJS-$(CONFIG_PCM_ALAW_DECODER)           += pcm.o
> diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> index 0243f47358..eff51f4042 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -651,6 +651,9 @@ static void register_all(void)
>      REGISTER_DECODER(XBIN,              xbin);
>      REGISTER_DECODER(IDF,               idf);
>  
> +    /* data */
> +    REGISTER_ENCODER(CAMERA_MOTION_METADATA, camera_motion_metadata);
> +
>      /* external libraries, that shouldn't be used by default if one of the
>       * above is available */
>      REGISTER_ENCDEC (LIBOPENH264,       libopenh264);
> diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
> index b697afa0ae..622383f453 100644
> --- a/libavcodec/avcodec.h
> +++ b/libavcodec/avcodec.h
> @@ -681,6 +681,7 @@ enum AVCodecID {
>      AV_CODEC_ID_DVD_NAV,
>      AV_CODEC_ID_TIMED_ID3,
>      AV_CODEC_ID_BIN_DATA,
> +    AV_CODEC_ID_CAMERA_MOTION_METADATA,
>  
>  
>      AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like AV_CODEC_ID_NONE) but lavf should attempt to identify it
> diff --git a/libavcodec/cammenc.c b/libavcodec/cammenc.c
> new file mode 100644
> index 0000000000..d7592ab69d
> --- /dev/null
> +++ b/libavcodec/cammenc.c
> @@ -0,0 +1,299 @@
> +/*
> + * Reference implementation for the CAMM Metadata encoder.
> + * Encodes sensor data for 360-degree cameras such as
> + * GPS, gyro, and acceleration. This is stored in a track separate from video
> + * and audio.
> + *
> + * Copyright (c) 2017 Louis O'Bryan
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * CAMM Metadata encoder
> + * @author Louis O'Bryan
> + */
> +
> +#include <math.h>
> +#include <float.h>
> +#include "avcodec.h"
> +#include "internal.h"
> +#include "bytestream.h"
> +#include "libavutil/common.h"
> +#include "libavutil/opt.h"
> +
> +#define DUMMY_ENCODER_SIZE 1
> +
> +typedef struct CammContext {
> +    AVCodecContext *avctx;
> +} CammContext;
> +
> +// Sizes of each type of metadata.
> +static int metadata_type_sizes[] = {
> +  3 * sizeof(float),
> +  2 * sizeof(uint64_t),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(double) + sizeof(uint32_t) + 7 * sizeof(float),
> +  3 * sizeof(float)
> +};
> +
> +static int min_packet_size = sizeof(uint16_t) * 2;

We pretty much don't accept mutable global variables in new code.

> +
> +/**
> + * Validates that the latitude has a valid value. Returns 1 if ok, else 0.
> + */
> +static int validate_latitude(AVCodecContext *avctx, double latitude) {
> +    if (latitude < -M_PI / 4 || latitude > M_PI / 4) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Latitude %f is not in bounds (%f, %f)\n",
> +               latitude, -M_PI / 4, M_PI / 4);
> +        return 0;
> +    }
> +    return 1;
> +}
> +
> +/**
> + * Validates that the longitude has a valid value. Returns 1 if ok, else 0.
> + */
> +static int validate_longitude(AVCodecContext *avctx, double longitude) {
> +    if (longitude < -M_PI / 2 || longitude > M_PI / 2) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "longitude %f is not in bounds (%f, %f)\n",
> +               longitude, -M_PI / 2, M_PI / 2);
> +        return 0;
> +    }
> +    return 1;
> +}
> +
> +static void log_float_values(AVCodecContext *avctx, const char *name, float *data) {
> +    int i;
> +    float value;
> +    for (i = 0; i < 3; ++i) {
> +        value = data[i];
> +        av_log(avctx, AV_LOG_DEBUG, "%s[%d] = %f\n", name, i, value);
> +    }
> +}
> +
> +/**
> + * Validates that the incoming data is correctly formatted and returns
> + * 1 if it is, 0 otherwise.
> + */
> +static int validate_data(AVCodecContext *avctx, const AVFrame *data) {
> +    uint16_t packet_type;
> +    int expected_packet_size;
> +    uint64_t pixel_exposure_time;
> +    uint64_t rolling_shutter_skew_time;
> +    uint32_t *camm_data;
> +    double time_gps_epoch;
> +    uint32_t gps_fix_type;
> +    double latitude;
> +    double longitude;
> +    float altitude;
> +
> +    if (data->pkt_size < min_packet_size) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "CAMM input data with size %d is too small\n", data->pkt_size);
> +        return 0;
> +    }

I don't think AVFrame.pkt_size should be used for any real purpose.
It's a hack and doesn't really have anything to do with AVFrame data
(the idea is that it corresponds to the source packet, but it's only a
hint at best).

I'm not sure what's the appropriate way to indicate the source data
size. Normally it's implicit (format and width/height for video, or
nb_samples for audio).

Where is the AVFrame format documented? Why is it somehow a raw byte
stream? AVFrame normally contains decoded data in a conveniently
accessible form. All this makes little sense to me to be honest.

> +
> +    packet_type = ((uint16_t*)(*data->extended_data))[1];
> +    if (packet_type > sizeof(metadata_type_sizes) / sizeof(int)) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Packet type %d is not recognized\n", packet_type);
> +        return 0;
> +    }
> +
> +    expected_packet_size = metadata_type_sizes[packet_type];
> +    if (expected_packet_size != data->pkt_size) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Packet size %d does not match expected size %d for type %d\n",
> +               data->pkt_size, expected_packet_size, packet_type);
> +        return 0;
> +    }
> +
> +    // The actual sensor data starts after the reserved slot and packet type.
> +    camm_data = ((uint32_t*)(*data->extended_data)) + 1;
> +    switch (packet_type) {
> +        // float angle_axis[3]
> +        case 0:
> +            log_float_values(avctx, "angle_axis", (float*)camm_data);
> +            break;
> +        // int32 pixel_exposure_time
> +        // int32 rolling_shutter_skew_time
> +        case 1:
> +            pixel_exposure_time = ((uint32_t*)camm_data)[0];
> +            av_log(avctx, AV_LOG_DEBUG,
> +                   "pixel_exposure_time = %lu\n", pixel_exposure_time);
> +            rolling_shutter_skew_time = ((uint32_t*)camm_data)[1];

Maybe I'm missing context, but these accesses are not endian safe, and
we still pretend to support big endian platforms in FFmpeg. Use macros
like AV_RL32(), which were designed for this. (AVFrame data can be in
native endian, but you memcpy() it just to a packet, suggesting an
"on wire" representation.)

> +            av_log(avctx, AV_LOG_DEBUG, "rolling_shutter_skew_time = %lu\n",
> +                   rolling_shutter_skew_time);

These log calls inflate the code quite a lot. Consider dropping them,
and moving dumping the contents somewhere else. (Like ffprobe.)

> +            break;
> +        // float gyro[3]
> +        case 2:
> +            log_float_values(avctx, "gyro", (float*)camm_data);
> +            break;
> +        case 3:
> +            log_float_values(avctx, "acceleration", (float*)camm_data);
> +            break;
> +        // float position[3]
> +        case 4:
> +            log_float_values(avctx, "position", (float*)camm_data);
> +            break;
> +        // float latitude
> +        // float longitude
> +        // float altitude
> +        case 5:
> +            if (!validate_latitude(avctx, ((float*)camm_data)[0])) return 0;
> +            if (!validate_longitude(avctx, ((float*)camm_data)[1])) return 0;
> +
> +            av_log(avctx, AV_LOG_DEBUG, "latitude = %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            av_log(avctx, AV_LOG_DEBUG, "longitude = %.6f\n",
> +                   ((float*)camm_data)[1]);
> +            av_log(avctx, AV_LOG_DEBUG, "altitude = %.6f\n",
> +                   ((float*)camm_data)[2]);
> +            break;
> +        case 6:
> +            time_gps_epoch = ((double*)camm_data)[0];
> +            if (time_gps_epoch < 0) {
> +                av_log(avctx, AV_LOG_ERROR,
> +                       "Time gps epoch %.6f is less than 0.\n",
> +                       time_gps_epoch);
> +                return 0;
> +            }
> +            av_log(avctx, AV_LOG_DEBUG, "time_gps_epoch = %.6f\n",
> +                   time_gps_epoch);
> +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> +
> +            gps_fix_type = ((uint32_t*)camm_data)[0];
> +            if (gps_fix_type != 0 && gps_fix_type != 1 && gps_fix_type != 2) {
> +                av_log(avctx, AV_LOG_ERROR,
> +                       "GPS fix type %d is not valid. Should be 0, 1, or 2\n",
> +                       gps_fix_type);
> +                return 0;
> +            }
> +            av_log(avctx, AV_LOG_DEBUG, "gps_fix_type = %d\n", gps_fix_type);
> +            camm_data = (uint32_t*) (((int32_t*)camm_data) + 1);
> +
> +            latitude = ((double*)camm_data)[0];
> +            if (!validate_latitude(avctx, latitude)) return 0;
> +            av_log(avctx, AV_LOG_DEBUG, "latitude = %.6f\n", latitude);
> +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> +
> +            longitude = ((double*)camm_data)[0];
> +            if (!validate_longitude(avctx, longitude)) return 0;
> +            av_log(avctx, AV_LOG_DEBUG, "longitude = %.6f\n", latitude);
> +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> +
> +            altitude = ((float*)camm_data)[0];
> +            av_log(avctx, AV_LOG_DEBUG, "altitude = %.6f\n", altitude);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "horizontal accuracy = %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "vertical accuracy %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "vertical east %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "vertical north %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "vertical up %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +
> +            av_log(avctx, AV_LOG_DEBUG, "speed accuracy %.6f\n",
> +                   ((float*)camm_data)[0]);
> +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> +            break;
> +        // float magnetic_field[3]
> +        case 7:
> +            log_float_values(avctx, "magnetic_field", (float*)camm_data);
> +            break;
> +    }
> +
> +    return 1;
> +}
> +
> +/**
> + * Encode camera motion metadata. Essentially, this encoder validates the
> + * input data and then just copies the data to an output AVPacket.
> + *  @param avctx          The AVCodecContext context.
> + *  @param packet         The output packet to write the encoded data.
> + *  @param data           The input camera sensor data.
> + *  @param got_packet     1 if a non-empty packet was returned, 0 otherwise.
> + */
> +static int encode_data(AVCodecContext *avctx, AVPacket *packet,
> +                        const AVFrame *data, int *got_packet)
> +{
> +    int ret;
> +
> +    if (!validate_data(avctx, data)) {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    // Make sure there is enough size allocated in packet->data.
> +    if ((ret = ff_alloc_packet2(avctx, packet, data->pkt_size, 0)) < 0) {
> +        av_log(avctx, AV_LOG_ERROR, "Can't allocate %d bytes in packet\n",
> +               data->pkt_size);
> +        return ret;
> +    }
> +
> +    // Copy necessary fields to the AVPacket.
> +    packet->pts = data->pts;
> +    packet->duration = data->pkt_duration;
> +    memcpy(packet->data, *(data->extended_data), data->pkt_size);

So why is this an encoder at all? What's the use? I suspect some
ffmpeg.c nonsense about just copying the data from a source file to a
destination file? But then you could probably use its codec copy path.

What is this encoder, which encodes nothing, supposed to be used for?

> +
> +    // Indicate that a packet was created.
> +    *got_packet = 1;
> +    return 0;
> +}
> +
> +/**
> + * Initializes the codec.
> + *
> + * @param avctx     The AVCodecContext context.
> + */
> +static av_cold int encode_init(AVCodecContext *avctx) {
> +    // Use dummy values for the height and width.
> +    avctx->width = DUMMY_ENCODER_SIZE;
> +    avctx->height = DUMMY_ENCODER_SIZE;
> +    avctx->max_pixels = DUMMY_ENCODER_SIZE;

What? This makes no sense.

> +
> +    return 0;
> +}
> +
> +// Define the encoder that ffmpeg will use for CAMM data.
> +AVCodec ff_camera_motion_metadata_encoder = {
> +    .name           = "camm",
> +    .long_name      = NULL_IF_CONFIG_SMALL("camera motion metadata"),
> +    .type           = AVMEDIA_TYPE_DATA,
> +    .id             = AV_CODEC_ID_CAMERA_MOTION_METADATA,
> +    .priv_data_size = sizeof(CammContext),
> +    .encode2        = encode_data,
> +    .init           = encode_init,
> +};
> diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
> index cf1246e431..014c1cd6b3 100644
> --- a/libavcodec/codec_desc.c
> +++ b/libavcodec/codec_desc.c
> @@ -3100,6 +3100,12 @@ static const AVCodecDescriptor codec_descriptors[] = {
>          .name      = "scte_35",
>          .long_name = NULL_IF_CONFIG_SMALL("SCTE 35 Message Queue"),
>      },
> +    {
> +        .id        = AV_CODEC_ID_CAMERA_MOTION_METADATA,
> +        .type      = AVMEDIA_TYPE_DATA,
> +        .name      = "camm",
> +        .long_name = NULL_IF_CONFIG_SMALL("camera motion metadata"),
> +    },
>  
>      /* deprecated codec ids */
>  };
> diff --git a/libavcodec/version.h b/libavcodec/version.h
> index 3c5fea9327..5b99785a72 100644
> --- a/libavcodec/version.h
> +++ b/libavcodec/version.h
> @@ -28,8 +28,8 @@
>  #include "libavutil/version.h"
>  
>  #define LIBAVCODEC_VERSION_MAJOR  57
> -#define LIBAVCODEC_VERSION_MINOR 100
> -#define LIBAVCODEC_VERSION_MICRO 103
> +#define LIBAVCODEC_VERSION_MINOR 101
> +#define LIBAVCODEC_VERSION_MICRO 100
>  
>  #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
>                                                 LIBAVCODEC_VERSION_MINOR, \
> diff --git a/libavformat/isom.c b/libavformat/isom.c
> index 3a9b3baf96..ca5107bf7f 100644
> --- a/libavformat/isom.c
> +++ b/libavformat/isom.c
> @@ -67,6 +67,7 @@ const AVCodecTag ff_mp4_obj_type[] = {
>      { AV_CODEC_ID_VORBIS      , 0xDD }, /* nonstandard, gpac uses it */
>      { AV_CODEC_ID_DVD_SUBTITLE, 0xE0 }, /* nonstandard, see unsupported-embedded-subs-2.mp4 */
>      { AV_CODEC_ID_QCELP       , 0xE1 },
> +    { AV_CODEC_ID_CAMERA_MOTION_METADATA, 0xE2 },
>      { AV_CODEC_ID_MPEG4SYSTEMS, 0x01 },
>      { AV_CODEC_ID_MPEG4SYSTEMS, 0x02 },
>      { AV_CODEC_ID_NONE        ,    0 },
> @@ -368,6 +369,11 @@ const AVCodecTag ff_codec_movsubtitle_tags[] = {
>      { AV_CODEC_ID_NONE, 0 },
>  };
>  
> +const AVCodecTag ff_codec_movdata_tags[] = {
> +    { AV_CODEC_ID_CAMERA_MOTION_METADATA, MKTAG('c','a','m','m') },
> +};
> +
> +
>  /* map numeric codes from mdhd atom to ISO 639 */
>  /* cf. QTFileFormat.pdf p253, qtff.pdf p205 */
>  /* http://developer.apple.com/documentation/mac/Text/Text-368.html */
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index 88f2f2c819..49528f8635 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -35,8 +35,6 @@
>  #include "libavcodec/dnxhddata.h"
>  #include "libavcodec/flac.h"
>  #include "libavcodec/get_bits.h"
> -
> -#include "libavcodec/internal.h"
>  #include "libavcodec/put_bits.h"
>  #include "libavcodec/vc1_common.h"
>  #include "libavcodec/raw.h"
> @@ -1126,10 +1124,7 @@ static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track)
>  
>      avio_wb32(pb, 0);
>      ffio_wfourcc(pb, "hvcC");
> -    if (track->tag == MKTAG('h','v','c','1'))
> -        ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 1);
> -    else
> -        ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
> +    ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
>      return update_size(pb, pos);
>  }
>  
> @@ -1230,6 +1225,60 @@ static int mov_write_dpxe_tag(AVIOContext *pb, MOVTrack *track)
>      return 0;
>  }
>  
> +static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track)
> +{
> +    int tag = track->par->codec_tag;
> +
> +    if (!ff_codec_get_tag(ff_mp4_obj_type, track->par->codec_id))
> +        return 0;
> +
> +    if      (track->par->codec_id == AV_CODEC_ID_H264)      tag = MKTAG('a','v','c','1');
> +    else if (track->par->codec_id == AV_CODEC_ID_HEVC)      tag = MKTAG('h','e','v','1');
> +    else if (track->par->codec_id == AV_CODEC_ID_VP9)       tag = MKTAG('v','p','0','9');
> +    else if (track->par->codec_id == AV_CODEC_ID_AC3)       tag = MKTAG('a','c','-','3');
> +    else if (track->par->codec_id == AV_CODEC_ID_EAC3)      tag = MKTAG('e','c','-','3');
> +    else if (track->par->codec_id == AV_CODEC_ID_DIRAC)     tag = MKTAG('d','r','a','c');
> +    else if (track->par->codec_id == AV_CODEC_ID_MOV_TEXT)  tag = MKTAG('t','x','3','g');
> +    else if (track->par->codec_id == AV_CODEC_ID_VC1)       tag = MKTAG('v','c','-','1');
> +    else if (track->par->codec_id == AV_CODEC_ID_FLAC)      tag = MKTAG('f','L','a','C');
> +    else if (track->par->codec_id == AV_CODEC_ID_OPUS)      tag = MKTAG('O','p','u','s');
> +    else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO)  tag = MKTAG('m','p','4','v');
> +    else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO)  tag = MKTAG('m','p','4','a');
> +    else if (track->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE)  tag = MKTAG('m','p','4','s');
> +
> +    return tag;
> +}
> +
> +static const AVCodecTag codec_ipod_tags[] = {
> +    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> +    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> +    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> +    { AV_CODEC_ID_ALAC,     MKTAG('a','l','a','c') },
> +    { AV_CODEC_ID_AC3,      MKTAG('a','c','-','3') },
> +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') },
> +    { AV_CODEC_ID_NONE, 0 },
> +};
> +
> +static int ipod_get_codec_tag(AVFormatContext *s, MOVTrack *track)
> +{
> +    int tag = track->par->codec_tag;
> +
> +    // keep original tag for subs, ipod supports both formats
> +    if (!(track->par->codec_type == AVMEDIA_TYPE_SUBTITLE &&
> +          (tag == MKTAG('t', 'x', '3', 'g') ||
> +           tag == MKTAG('t', 'e', 'x', 't'))))
> +        tag = ff_codec_get_tag(codec_ipod_tags, track->par->codec_id);
> +
> +    if (!av_match_ext(s->filename, "m4a") &&
> +        !av_match_ext(s->filename, "m4b") &&
> +        !av_match_ext(s->filename, "m4v"))
> +        av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a, .m4v nor  .m4b "
> +               "Quicktime/Ipod might not play the file\n");
> +
> +    return tag;
> +}
> +
>  static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track)
>  {
>      int tag;
> @@ -1506,25 +1555,42 @@ static int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track)
>      return tag;
>  }
>  
> +static const AVCodecTag codec_3gp_tags[] = {
> +    { AV_CODEC_ID_H263,     MKTAG('s','2','6','3') },
> +    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> +    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> +    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> +    { AV_CODEC_ID_AMR_NB,   MKTAG('s','a','m','r') },
> +    { AV_CODEC_ID_AMR_WB,   MKTAG('s','a','w','b') },
> +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> +    { AV_CODEC_ID_NONE, 0 },
> +};
> +
> +static const AVCodecTag codec_f4v_tags[] = { // XXX: add GIF/PNG/JPEG?
> +    { AV_CODEC_ID_MP3,    MKTAG('.','m','p','3') },
> +    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
> +    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
> +    { AV_CODEC_ID_VP6A,   MKTAG('V','P','6','A') },
> +    { AV_CODEC_ID_VP6F,   MKTAG('V','P','6','F') },
> +    { AV_CODEC_ID_NONE, 0 },
> +};
> +
>  static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track)
>  {
>      int tag;
>  
>      if (track->mode == MODE_MP4 || track->mode == MODE_PSP)
> -        tag = track->par->codec_tag;
> -    else if (track->mode == MODE_ISM)
> -        tag = track->par->codec_tag;
> -    else if (track->mode == MODE_IPOD) {
> -        if (!av_match_ext(s->filename, "m4a") &&
> -            !av_match_ext(s->filename, "m4v") &&
> -            !av_match_ext(s->filename, "m4b"))
> -            av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor .m4v "
> -                   "Quicktime/Ipod might not play the file\n");
> -        tag = track->par->codec_tag;
> -    } else if (track->mode & MODE_3GP)
> -        tag = track->par->codec_tag;
> +        tag = mp4_get_codec_tag(s, track);
> +    else if (track->mode == MODE_ISM) {
> +        tag = mp4_get_codec_tag(s, track);
> +        if (!tag && track->par->codec_id == AV_CODEC_ID_WMAPRO)
> +            tag = MKTAG('w', 'm', 'a', ' ');
> +    } else if (track->mode == MODE_IPOD)
> +        tag = ipod_get_codec_tag(s, track);
> +    else if (track->mode & MODE_3GP)
> +        tag = ff_codec_get_tag(codec_3gp_tags, track->par->codec_id);
>      else if (track->mode == MODE_F4V)
> -        tag = track->par->codec_tag;
> +        tag = ff_codec_get_tag(codec_f4v_tags, track->par->codec_id);
>      else
>          tag = mov_get_codec_tag(s, track);
>  
> @@ -2060,6 +2126,19 @@ static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track)
>      return update_size(pb, pos);
>  }
>  
> +static int mov_write_camm_tag(AVIOContext *pb) {
> +    int64_t size_update;
> +    int64_t pos = avio_tell(pb);
> +    avio_wb32(pb, 0); /* size */
> +    ffio_wfourcc(pb, "camm");
> +    avio_wb32(pb, 0); /* Reserved */
> +    avio_wb16(pb, 0); /* Reserved */
> +    avio_wb16(pb, 1); /* Data-reference index */
> +
> +    size_update = update_size(pb, pos);
> +    return size_update;
> +}
> +
>  static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
>  {
>      int64_t pos = avio_tell(pb);
> @@ -2077,6 +2156,10 @@ static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext
>          mov_write_rtp_tag(pb, track);
>      else if (track->par->codec_tag == MKTAG('t','m','c','d'))
>          mov_write_tmcd_tag(pb, track);
> +    else if (track->par->codec_tag
> +             == ff_codec_get_tag(ff_mp4_obj_type,
> +                                 AV_CODEC_ID_CAMERA_MOTION_METADATA))
> +        mov_write_camm_tag(pb);
>      return update_size(pb, pos);
>  }
>  
> @@ -2443,6 +2526,11 @@ static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra
>          } else if (track->par->codec_tag == MKTAG('t','m','c','d')) {
>              hdlr_type = "tmcd";
>              descr = "TimeCodeHandler";
> +        } else if (track->par->codec_tag
> +                       == ff_codec_get_tag(ff_mp4_obj_type,
> +                            AV_CODEC_ID_CAMERA_MOTION_METADATA)) {
> +            hdlr_type = "camm";
> +            descr = "CameraMetadataMotionHandler";
>          } else {
>              av_log(s, AV_LOG_WARNING,
>                     "Unknown hldr_type for %s, writing dummy values\n",
> @@ -6422,73 +6510,6 @@ static int mov_check_bitstream(struct AVFormatContext *s, const AVPacket *pkt)
>      return ret;
>  }
>  
> -static const AVCodecTag codec_3gp_tags[] = {
> -    { AV_CODEC_ID_H263,     MKTAG('s','2','6','3') },
> -    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> -    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> -    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> -    { AV_CODEC_ID_AMR_NB,   MKTAG('s','a','m','r') },
> -    { AV_CODEC_ID_AMR_WB,   MKTAG('s','a','w','b') },
> -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> -    { AV_CODEC_ID_NONE, 0 },
> -};
> -
> -const AVCodecTag codec_mp4_tags[] = {
> -    { AV_CODEC_ID_MPEG4       , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_H264        , MKTAG('a', 'v', 'c', '1') },
> -    { AV_CODEC_ID_HEVC        , MKTAG('h', 'e', 'v', '1') },
> -    { AV_CODEC_ID_HEVC        , MKTAG('h', 'v', 'c', '1') },
> -    { AV_CODEC_ID_MPEG2VIDEO  , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_MPEG1VIDEO  , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_MJPEG       , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_PNG         , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_JPEG2000    , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_VC1         , MKTAG('v', 'c', '-', '1') },
> -    { AV_CODEC_ID_DIRAC       , MKTAG('d', 'r', 'a', 'c') },
> -    { AV_CODEC_ID_TSCC2       , MKTAG('m', 'p', '4', 'v') },
> -    { AV_CODEC_ID_VP9         , MKTAG('v', 'p', '0', '9') },
> -    { AV_CODEC_ID_AAC         , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_MP4ALS      , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_MP3         , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_MP2         , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_AC3         , MKTAG('a', 'c', '-', '3') },
> -    { AV_CODEC_ID_EAC3        , MKTAG('e', 'c', '-', '3') },
> -    { AV_CODEC_ID_DTS         , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_FLAC        , MKTAG('f', 'L', 'a', 'C') },
> -    { AV_CODEC_ID_OPUS        , MKTAG('O', 'p', 'u', 's') },
> -    { AV_CODEC_ID_VORBIS      , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_QCELP       , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_EVRC        , MKTAG('m', 'p', '4', 'a') },
> -    { AV_CODEC_ID_DVD_SUBTITLE, MKTAG('m', 'p', '4', 's') },
> -    { AV_CODEC_ID_MOV_TEXT    , MKTAG('t', 'x', '3', 'g') },
> -    { AV_CODEC_ID_NONE        ,    0 },
> -};
> -
> -const AVCodecTag codec_ism_tags[] = {
> -    { AV_CODEC_ID_WMAPRO      , MKTAG('w', 'm', 'a', ' ') },
> -    { AV_CODEC_ID_NONE        ,    0 },
> -};
> -
> -static const AVCodecTag codec_ipod_tags[] = {
> -    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> -    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> -    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> -    { AV_CODEC_ID_ALAC,     MKTAG('a','l','a','c') },
> -    { AV_CODEC_ID_AC3,      MKTAG('a','c','-','3') },
> -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') },
> -    { AV_CODEC_ID_NONE, 0 },
> -};
> -
> -static const AVCodecTag codec_f4v_tags[] = {
> -    { AV_CODEC_ID_MP3,    MKTAG('.','m','p','3') },
> -    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
> -    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
> -    { AV_CODEC_ID_VP6A,   MKTAG('V','P','6','A') },
> -    { AV_CODEC_ID_VP6F,   MKTAG('V','P','6','F') },
> -    { AV_CODEC_ID_NONE, 0 },
> -};
> -
>  #if CONFIG_MOV_MUXER
>  MOV_CLASS(mov)
>  AVOutputFormat ff_mov_muxer = {
> @@ -6549,7 +6570,7 @@ AVOutputFormat ff_mp4_muxer = {
>      .write_trailer     = mov_write_trailer,
>      .deinit            = mov_free,
>      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
> -    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags, 0 },
> +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
>      .check_bitstream   = mov_check_bitstream,
>      .priv_class        = &mp4_muxer_class,
>  };
> @@ -6570,7 +6591,7 @@ AVOutputFormat ff_psp_muxer = {
>      .write_trailer     = mov_write_trailer,
>      .deinit            = mov_free,
>      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
> -    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags, 0 },
> +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
>      .check_bitstream   = mov_check_bitstream,
>      .priv_class        = &psp_muxer_class,
>  };
> @@ -6632,8 +6653,7 @@ AVOutputFormat ff_ismv_muxer = {
>      .write_trailer     = mov_write_trailer,
>      .deinit            = mov_free,
>      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
> -    .codec_tag         = (const AVCodecTag* const []){
> -        codec_mp4_tags, codec_ism_tags, 0 },
> +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
>      .check_bitstream   = mov_check_bitstream,
>      .priv_class        = &ismv_muxer_class,
>  };

This stuff apparently got in by mistake?
Reimar Döffinger July 7, 2017, 10:12 p.m. UTC | #4
I don't know what you are trying to achieve, but an encoder that just copies things through makes no sense.
If it's just about validating or logging some kind of parser or (bitstream-)filter probably makes more sense.
Even if doing it as encoder, why should it be a video encoder instead of something like raw (ok, not sure we support that) or subtitle?

> +// Sizes of each type of metadata.
> +static int metadata_type_sizes[] = {
> +  3 * sizeof(float),
> +  2 * sizeof(uint64_t),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(float),
> +  3 * sizeof(double) + sizeof(uint32_t) + 7 * sizeof(float),
> +  3 * sizeof(float)
> +};
> +
> +static int min_packet_size = sizeof(uint16_t) * 2;

These miss "const".
And as someone else mentioned, all the data reading needs to use special functions.
If it's still the same as when I was more active, that includes the float values.
Not all platforms use IEEE floats and there used to be platforms where float values had an endiannes different from the integers etc.
You should probably also consider if you validate against things like M_PI / 4 how this deals with rounding errors.
If you had a spec, you would probably have the exact bit pattern of the minimum and maximum and you should then do a total order comparison against that, preferably without even converting to double.

> +static int validate_latitude(AVCodecContext *avctx, double latitude) {
> +    if (latitude < -M_PI / 4 || latitude > M_PI / 4) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Latitude %f is not in bounds (%f, %f)\n",
> +               latitude, -M_PI / 4, M_PI / 4);
> +        return 0;
> +    }

This one in particular will allow all NaNs as valid, which I suspect is not the intention, and if it is should be made more explicit.
Louis O'Bryan July 7, 2017, 10:24 p.m. UTC | #5
Thanks for the feedback.

I don't think AVFrame.pkt_size should be used for any real purpose.
> It's a hack and doesn't really have anything to do with AVFrame data
> (the idea is that it corresponds to the source packet, but it's only a
> hint at best).

The AVFrame format is a raw byte stream with a packet type and variable
length depending on the data type. What is the best way to indicate the
data size, given that this isn't audio or video?

What is this encoder, which encodes nothing, supposed to be used for?

The use case is to write data to a new stream in the mp4 container. The
encoder isn't changing the data. This data would reside in the same mp4
container as video and audio streams. Are you suggesting there is a way I
can accomplish that task without creating this encoder (and if so, how)?

> +static av_cold int encode_init(AVCodecContext *avctx) {
> > +    // Use dummy values for the height and width.
> > +    avctx->width = DUMMY_ENCODER_SIZE;
> > +    avctx->height = DUMMY_ENCODER_SIZE;
> > +    avctx->max_pixels = DUMMY_ENCODER_SIZE;
> What? This makes no sense.

Using avcodec_encode_video2() seems to require that the width and height be
nonzero. What is the recommended way to avoid that?


On Fri, Jul 7, 2017 at 3:45 AM, wm4 <nfxjfg@googlemail.com> wrote:

> On Thu,  6 Jul 2017 17:36:39 -0700
> "Louis O'Bryan" <lboextdev@gmail.com> wrote:
>
> > From: Louis O'Bryan <louiso@google.com>
> >
> > Signed-off-by: Louis O'Bryan <louiso@google.com>
> > ---
> >  Changelog               |   1 +
> >  doc/general.texi        |   2 +
> >  libavcodec/Makefile     |   1 +
> >  libavcodec/allcodecs.c  |   3 +
> >  libavcodec/avcodec.h    |   1 +
> >  libavcodec/cammenc.c    | 299 ++++++++++++++++++++++++++++++
> ++++++++++++++++++
> >  libavcodec/codec_desc.c |   6 +
> >  libavcodec/version.h    |   4 +-
> >  libavformat/isom.c      |   6 +
> >  libavformat/movenc.c    | 200 +++++++++++++++++---------------
> >  10 files changed, 431 insertions(+), 92 deletions(-)
> >  create mode 100644 libavcodec/cammenc.c
> >
> > diff --git a/Changelog b/Changelog
> > index a8726c6736..5f98385b53 100644
> > --- a/Changelog
> > +++ b/Changelog
> > @@ -2,6 +2,7 @@ Entries are sorted chronologically from oldest to
> youngest within each release,
> >  releases are sorted from youngest to oldest.
> >
> >  version <next>:
> > +- Camera metadata motion encoder
> >  - deflicker video filter
> >  - doubleweave video filter
> >  - lumakey video filter
> > diff --git a/doc/general.texi b/doc/general.texi
> > index 8f582d586f..06996c81e8 100644
> > --- a/doc/general.texi
> > +++ b/doc/general.texi
> > @@ -912,6 +912,8 @@ following image formats are supported:
> >      @tab part of LCL, encoder experimental
> >  @item Zip Motion Blocks Video  @tab   X @tab  X
> >      @tab Encoder works only in PAL8.
> > +@item Camera metadata motion    @tab X  @tab
> > +    @tab Encoder for camera sensor data.
> >  @end multitable
> >
> >  @code{X} means that encoding (resp. decoding) is supported.
> > diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> > index b440a00746..306cc793ee 100644
> > --- a/libavcodec/Makefile
> > +++ b/libavcodec/Makefile
> > @@ -680,6 +680,7 @@ OBJS-$(CONFIG_ZLIB_DECODER)            += lcldec.o
> >  OBJS-$(CONFIG_ZLIB_ENCODER)            += lclenc.o
> >  OBJS-$(CONFIG_ZMBV_DECODER)            += zmbv.o
> >  OBJS-$(CONFIG_ZMBV_ENCODER)            += zmbvenc.o
> > +OBJS-$(CONFIG_CAMERA_MOTION_METADATA_ENCODER) += cammenc.o
> >
> >  # (AD)PCM decoders/encoders
> >  OBJS-$(CONFIG_PCM_ALAW_DECODER)           += pcm.o
> > diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> > index 0243f47358..eff51f4042 100644
> > --- a/libavcodec/allcodecs.c
> > +++ b/libavcodec/allcodecs.c
> > @@ -651,6 +651,9 @@ static void register_all(void)
> >      REGISTER_DECODER(XBIN,              xbin);
> >      REGISTER_DECODER(IDF,               idf);
> >
> > +    /* data */
> > +    REGISTER_ENCODER(CAMERA_MOTION_METADATA, camera_motion_metadata);
> > +
> >      /* external libraries, that shouldn't be used by default if one of
> the
> >       * above is available */
> >      REGISTER_ENCDEC (LIBOPENH264,       libopenh264);
> > diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
> > index b697afa0ae..622383f453 100644
> > --- a/libavcodec/avcodec.h
> > +++ b/libavcodec/avcodec.h
> > @@ -681,6 +681,7 @@ enum AVCodecID {
> >      AV_CODEC_ID_DVD_NAV,
> >      AV_CODEC_ID_TIMED_ID3,
> >      AV_CODEC_ID_BIN_DATA,
> > +    AV_CODEC_ID_CAMERA_MOTION_METADATA,
> >
> >
> >      AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like
> AV_CODEC_ID_NONE) but lavf should attempt to identify it
> > diff --git a/libavcodec/cammenc.c b/libavcodec/cammenc.c
> > new file mode 100644
> > index 0000000000..d7592ab69d
> > --- /dev/null
> > +++ b/libavcodec/cammenc.c
> > @@ -0,0 +1,299 @@
> > +/*
> > + * Reference implementation for the CAMM Metadata encoder.
> > + * Encodes sensor data for 360-degree cameras such as
> > + * GPS, gyro, and acceleration. This is stored in a track separate from
> video
> > + * and audio.
> > + *
> > + * Copyright (c) 2017 Louis O'Bryan
> > + *
> > + * This file is part of FFmpeg.
> > + *
> > + * FFmpeg is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU Lesser General Public
> > + * License as published by the Free Software Foundation; either
> > + * version 2.1 of the License, or (at your option) any later version.
> > + *
> > + * FFmpeg is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > + * Lesser General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU Lesser General Public
> > + * License along with FFmpeg; if not, write to the Free Software
> > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA
> > + */
> > +
> > +/**
> > + * CAMM Metadata encoder
> > + * @author Louis O'Bryan
> > + */
> > +
> > +#include <math.h>
> > +#include <float.h>
> > +#include "avcodec.h"
> > +#include "internal.h"
> > +#include "bytestream.h"
> > +#include "libavutil/common.h"
> > +#include "libavutil/opt.h"
> > +
> > +#define DUMMY_ENCODER_SIZE 1
> > +
> > +typedef struct CammContext {
> > +    AVCodecContext *avctx;
> > +} CammContext;
> > +
> > +// Sizes of each type of metadata.
> > +static int metadata_type_sizes[] = {
> > +  3 * sizeof(float),
> > +  2 * sizeof(uint64_t),
> > +  3 * sizeof(float),
> > +  3 * sizeof(float),
> > +  3 * sizeof(float),
> > +  3 * sizeof(float),
> > +  3 * sizeof(double) + sizeof(uint32_t) + 7 * sizeof(float),
> > +  3 * sizeof(float)
> > +};
> > +
> > +static int min_packet_size = sizeof(uint16_t) * 2;
>
> We pretty much don't accept mutable global variables in new code.
>
> > +
> > +/**
> > + * Validates that the latitude has a valid value. Returns 1 if ok, else
> 0.
> > + */
> > +static int validate_latitude(AVCodecContext *avctx, double latitude) {
> > +    if (latitude < -M_PI / 4 || latitude > M_PI / 4) {
> > +        av_log(avctx, AV_LOG_ERROR,
> > +               "Latitude %f is not in bounds (%f, %f)\n",
> > +               latitude, -M_PI / 4, M_PI / 4);
> > +        return 0;
> > +    }
> > +    return 1;
> > +}
> > +
> > +/**
> > + * Validates that the longitude has a valid value. Returns 1 if ok,
> else 0.
> > + */
> > +static int validate_longitude(AVCodecContext *avctx, double longitude)
> {
> > +    if (longitude < -M_PI / 2 || longitude > M_PI / 2) {
> > +        av_log(avctx, AV_LOG_ERROR,
> > +               "longitude %f is not in bounds (%f, %f)\n",
> > +               longitude, -M_PI / 2, M_PI / 2);
> > +        return 0;
> > +    }
> > +    return 1;
> > +}
> > +
> > +static void log_float_values(AVCodecContext *avctx, const char *name,
> float *data) {
> > +    int i;
> > +    float value;
> > +    for (i = 0; i < 3; ++i) {
> > +        value = data[i];
> > +        av_log(avctx, AV_LOG_DEBUG, "%s[%d] = %f\n", name, i, value);
> > +    }
> > +}
> > +
> > +/**
> > + * Validates that the incoming data is correctly formatted and returns
> > + * 1 if it is, 0 otherwise.
> > + */
> > +static int validate_data(AVCodecContext *avctx, const AVFrame *data) {
> > +    uint16_t packet_type;
> > +    int expected_packet_size;
> > +    uint64_t pixel_exposure_time;
> > +    uint64_t rolling_shutter_skew_time;
> > +    uint32_t *camm_data;
> > +    double time_gps_epoch;
> > +    uint32_t gps_fix_type;
> > +    double latitude;
> > +    double longitude;
> > +    float altitude;
> > +
> > +    if (data->pkt_size < min_packet_size) {
> > +        av_log(avctx, AV_LOG_ERROR,
> > +               "CAMM input data with size %d is too small\n",
> data->pkt_size);
> > +        return 0;
> > +    }
>
> I don't think AVFrame.pkt_size should be used for any real purpose.
> It's a hack and doesn't really have anything to do with AVFrame data
> (the idea is that it corresponds to the source packet, but it's only a
> hint at best).
>
> I'm not sure what's the appropriate way to indicate the source data
> size. Normally it's implicit (format and width/height for video, or
> nb_samples for audio).
>
> Where is the AVFrame format documented? Why is it somehow a raw byte
> stream? AVFrame normally contains decoded data in a conveniently
> accessible form. All this makes little sense to me to be honest.
>
> > +
> > +    packet_type = ((uint16_t*)(*data->extended_data))[1];
> > +    if (packet_type > sizeof(metadata_type_sizes) / sizeof(int)) {
> > +        av_log(avctx, AV_LOG_ERROR,
> > +               "Packet type %d is not recognized\n", packet_type);
> > +        return 0;
> > +    }
> > +
> > +    expected_packet_size = metadata_type_sizes[packet_type];
> > +    if (expected_packet_size != data->pkt_size) {
> > +        av_log(avctx, AV_LOG_ERROR,
> > +               "Packet size %d does not match expected size %d for type
> %d\n",
> > +               data->pkt_size, expected_packet_size, packet_type);
> > +        return 0;
> > +    }
> > +
> > +    // The actual sensor data starts after the reserved slot and packet
> type.
> > +    camm_data = ((uint32_t*)(*data->extended_data)) + 1;
> > +    switch (packet_type) {
> > +        // float angle_axis[3]
> > +        case 0:
> > +            log_float_values(avctx, "angle_axis", (float*)camm_data);
> > +            break;
> > +        // int32 pixel_exposure_time
> > +        // int32 rolling_shutter_skew_time
> > +        case 1:
> > +            pixel_exposure_time = ((uint32_t*)camm_data)[0];
> > +            av_log(avctx, AV_LOG_DEBUG,
> > +                   "pixel_exposure_time = %lu\n", pixel_exposure_time);
> > +            rolling_shutter_skew_time = ((uint32_t*)camm_data)[1];
>
> Maybe I'm missing context, but these accesses are not endian safe, and
> we still pretend to support big endian platforms in FFmpeg. Use macros
> like AV_RL32(), which were designed for this. (AVFrame data can be in
> native endian, but you memcpy() it just to a packet, suggesting an
> "on wire" representation.)
>
> > +            av_log(avctx, AV_LOG_DEBUG, "rolling_shutter_skew_time =
> %lu\n",
> > +                   rolling_shutter_skew_time);
>
> These log calls inflate the code quite a lot. Consider dropping them,
> and moving dumping the contents somewhere else. (Like ffprobe.)
>
> > +            break;
> > +        // float gyro[3]
> > +        case 2:
> > +            log_float_values(avctx, "gyro", (float*)camm_data);
> > +            break;
> > +        case 3:
> > +            log_float_values(avctx, "acceleration", (float*)camm_data);
> > +            break;
> > +        // float position[3]
> > +        case 4:
> > +            log_float_values(avctx, "position", (float*)camm_data);
> > +            break;
> > +        // float latitude
> > +        // float longitude
> > +        // float altitude
> > +        case 5:
> > +            if (!validate_latitude(avctx, ((float*)camm_data)[0]))
> return 0;
> > +            if (!validate_longitude(avctx, ((float*)camm_data)[1]))
> return 0;
> > +
> > +            av_log(avctx, AV_LOG_DEBUG, "latitude = %.6f\n",
> > +                   ((float*)camm_data)[0]);
> > +            av_log(avctx, AV_LOG_DEBUG, "longitude = %.6f\n",
> > +                   ((float*)camm_data)[1]);
> > +            av_log(avctx, AV_LOG_DEBUG, "altitude = %.6f\n",
> > +                   ((float*)camm_data)[2]);
> > +            break;
> > +        case 6:
> > +            time_gps_epoch = ((double*)camm_data)[0];
> > +            if (time_gps_epoch < 0) {
> > +                av_log(avctx, AV_LOG_ERROR,
> > +                       "Time gps epoch %.6f is less than 0.\n",
> > +                       time_gps_epoch);
> > +                return 0;
> > +            }
> > +            av_log(avctx, AV_LOG_DEBUG, "time_gps_epoch = %.6f\n",
> > +                   time_gps_epoch);
> > +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> > +
> > +            gps_fix_type = ((uint32_t*)camm_data)[0];
> > +            if (gps_fix_type != 0 && gps_fix_type != 1 && gps_fix_type
> != 2) {
> > +                av_log(avctx, AV_LOG_ERROR,
> > +                       "GPS fix type %d is not valid. Should be 0, 1,
> or 2\n",
> > +                       gps_fix_type);
> > +                return 0;
> > +            }
> > +            av_log(avctx, AV_LOG_DEBUG, "gps_fix_type = %d\n",
> gps_fix_type);
> > +            camm_data = (uint32_t*) (((int32_t*)camm_data) + 1);
> > +
> > +            latitude = ((double*)camm_data)[0];
> > +            if (!validate_latitude(avctx, latitude)) return 0;
> > +            av_log(avctx, AV_LOG_DEBUG, "latitude = %.6f\n", latitude);
> > +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> > +
> > +            longitude = ((double*)camm_data)[0];
> > +            if (!validate_longitude(avctx, longitude)) return 0;
> > +            av_log(avctx, AV_LOG_DEBUG, "longitude = %.6f\n", latitude);
> > +            camm_data = (uint32_t*) (((double*)camm_data) + 1);
> > +
> > +            altitude = ((float*)camm_data)[0];
> > +            av_log(avctx, AV_LOG_DEBUG, "altitude = %.6f\n", altitude);
> > +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> > +
> > +            av_log(avctx, AV_LOG_DEBUG, "horizontal accuracy = %.6f\n",
> > +                   ((float*)camm_data)[0]);
> > +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> > +
> > +            av_log(avctx, AV_LOG_DEBUG, "vertical accuracy %.6f\n",
> > +                   ((float*)camm_data)[0]);
> > +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> > +
> > +            av_log(avctx, AV_LOG_DEBUG, "vertical east %.6f\n",
> > +                   ((float*)camm_data)[0]);
> > +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> > +
> > +            av_log(avctx, AV_LOG_DEBUG, "vertical north %.6f\n",
> > +                   ((float*)camm_data)[0]);
> > +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> > +
> > +            av_log(avctx, AV_LOG_DEBUG, "vertical up %.6f\n",
> > +                   ((float*)camm_data)[0]);
> > +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> > +
> > +            av_log(avctx, AV_LOG_DEBUG, "speed accuracy %.6f\n",
> > +                   ((float*)camm_data)[0]);
> > +            camm_data = (uint32_t*) (((float*)camm_data) + 1);
> > +            break;
> > +        // float magnetic_field[3]
> > +        case 7:
> > +            log_float_values(avctx, "magnetic_field",
> (float*)camm_data);
> > +            break;
> > +    }
> > +
> > +    return 1;
> > +}
> > +
> > +/**
> > + * Encode camera motion metadata. Essentially, this encoder validates
> the
> > + * input data and then just copies the data to an output AVPacket.
> > + *  @param avctx          The AVCodecContext context.
> > + *  @param packet         The output packet to write the encoded data.
> > + *  @param data           The input camera sensor data.
> > + *  @param got_packet     1 if a non-empty packet was returned, 0
> otherwise.
> > + */
> > +static int encode_data(AVCodecContext *avctx, AVPacket *packet,
> > +                        const AVFrame *data, int *got_packet)
> > +{
> > +    int ret;
> > +
> > +    if (!validate_data(avctx, data)) {
> > +        return AVERROR(EINVAL);
> > +    }
> > +
> > +    // Make sure there is enough size allocated in packet->data.
> > +    if ((ret = ff_alloc_packet2(avctx, packet, data->pkt_size, 0)) < 0)
> {
> > +        av_log(avctx, AV_LOG_ERROR, "Can't allocate %d bytes in
> packet\n",
> > +               data->pkt_size);
> > +        return ret;
> > +    }
> > +
> > +    // Copy necessary fields to the AVPacket.
> > +    packet->pts = data->pts;
> > +    packet->duration = data->pkt_duration;
> > +    memcpy(packet->data, *(data->extended_data), data->pkt_size);
>
> So why is this an encoder at all? What's the use? I suspect some
> ffmpeg.c nonsense about just copying the data from a source file to a
> destination file? But then you could probably use its codec copy path.
>
> What is this encoder, which encodes nothing, supposed to be used for?
>
> > +
> > +    // Indicate that a packet was created.
> > +    *got_packet = 1;
> > +    return 0;
> > +}
> > +
> > +/**
> > + * Initializes the codec.
> > + *
> > + * @param avctx     The AVCodecContext context.
> > + */
> > +static av_cold int encode_init(AVCodecContext *avctx) {
> > +    // Use dummy values for the height and width.
> > +    avctx->width = DUMMY_ENCODER_SIZE;
> > +    avctx->height = DUMMY_ENCODER_SIZE;
> > +    avctx->max_pixels = DUMMY_ENCODER_SIZE;
>
> What? This makes no sense.
>
> > +
> > +    return 0;
> > +}
> > +
> > +// Define the encoder that ffmpeg will use for CAMM data.
> > +AVCodec ff_camera_motion_metadata_encoder = {
> > +    .name           = "camm",
> > +    .long_name      = NULL_IF_CONFIG_SMALL("camera motion metadata"),
> > +    .type           = AVMEDIA_TYPE_DATA,
> > +    .id             = AV_CODEC_ID_CAMERA_MOTION_METADATA,
> > +    .priv_data_size = sizeof(CammContext),
> > +    .encode2        = encode_data,
> > +    .init           = encode_init,
> > +};
> > diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
> > index cf1246e431..014c1cd6b3 100644
> > --- a/libavcodec/codec_desc.c
> > +++ b/libavcodec/codec_desc.c
> > @@ -3100,6 +3100,12 @@ static const AVCodecDescriptor
> codec_descriptors[] = {
> >          .name      = "scte_35",
> >          .long_name = NULL_IF_CONFIG_SMALL("SCTE 35 Message Queue"),
> >      },
> > +    {
> > +        .id        = AV_CODEC_ID_CAMERA_MOTION_METADATA,
> > +        .type      = AVMEDIA_TYPE_DATA,
> > +        .name      = "camm",
> > +        .long_name = NULL_IF_CONFIG_SMALL("camera motion metadata"),
> > +    },
> >
> >      /* deprecated codec ids */
> >  };
> > diff --git a/libavcodec/version.h b/libavcodec/version.h
> > index 3c5fea9327..5b99785a72 100644
> > --- a/libavcodec/version.h
> > +++ b/libavcodec/version.h
> > @@ -28,8 +28,8 @@
> >  #include "libavutil/version.h"
> >
> >  #define LIBAVCODEC_VERSION_MAJOR  57
> > -#define LIBAVCODEC_VERSION_MINOR 100
> > -#define LIBAVCODEC_VERSION_MICRO 103
> > +#define LIBAVCODEC_VERSION_MINOR 101
> > +#define LIBAVCODEC_VERSION_MICRO 100
> >
> >  #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR,
> \
> >
>  LIBAVCODEC_VERSION_MINOR, \
> > diff --git a/libavformat/isom.c b/libavformat/isom.c
> > index 3a9b3baf96..ca5107bf7f 100644
> > --- a/libavformat/isom.c
> > +++ b/libavformat/isom.c
> > @@ -67,6 +67,7 @@ const AVCodecTag ff_mp4_obj_type[] = {
> >      { AV_CODEC_ID_VORBIS      , 0xDD }, /* nonstandard, gpac uses it */
> >      { AV_CODEC_ID_DVD_SUBTITLE, 0xE0 }, /* nonstandard, see
> unsupported-embedded-subs-2.mp4 */
> >      { AV_CODEC_ID_QCELP       , 0xE1 },
> > +    { AV_CODEC_ID_CAMERA_MOTION_METADATA, 0xE2 },
> >      { AV_CODEC_ID_MPEG4SYSTEMS, 0x01 },
> >      { AV_CODEC_ID_MPEG4SYSTEMS, 0x02 },
> >      { AV_CODEC_ID_NONE        ,    0 },
> > @@ -368,6 +369,11 @@ const AVCodecTag ff_codec_movsubtitle_tags[] = {
> >      { AV_CODEC_ID_NONE, 0 },
> >  };
> >
> > +const AVCodecTag ff_codec_movdata_tags[] = {
> > +    { AV_CODEC_ID_CAMERA_MOTION_METADATA, MKTAG('c','a','m','m') },
> > +};
> > +
> > +
> >  /* map numeric codes from mdhd atom to ISO 639 */
> >  /* cf. QTFileFormat.pdf p253, qtff.pdf p205 */
> >  /* http://developer.apple.com/documentation/mac/Text/Text-368.html */
> > diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> > index 88f2f2c819..49528f8635 100644
> > --- a/libavformat/movenc.c
> > +++ b/libavformat/movenc.c
> > @@ -35,8 +35,6 @@
> >  #include "libavcodec/dnxhddata.h"
> >  #include "libavcodec/flac.h"
> >  #include "libavcodec/get_bits.h"
> > -
> > -#include "libavcodec/internal.h"
> >  #include "libavcodec/put_bits.h"
> >  #include "libavcodec/vc1_common.h"
> >  #include "libavcodec/raw.h"
> > @@ -1126,10 +1124,7 @@ static int mov_write_hvcc_tag(AVIOContext *pb,
> MOVTrack *track)
> >
> >      avio_wb32(pb, 0);
> >      ffio_wfourcc(pb, "hvcC");
> > -    if (track->tag == MKTAG('h','v','c','1'))
> > -        ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 1);
> > -    else
> > -        ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
> > +    ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
> >      return update_size(pb, pos);
> >  }
> >
> > @@ -1230,6 +1225,60 @@ static int mov_write_dpxe_tag(AVIOContext *pb,
> MOVTrack *track)
> >      return 0;
> >  }
> >
> > +static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track)
> > +{
> > +    int tag = track->par->codec_tag;
> > +
> > +    if (!ff_codec_get_tag(ff_mp4_obj_type, track->par->codec_id))
> > +        return 0;
> > +
> > +    if      (track->par->codec_id == AV_CODEC_ID_H264)      tag =
> MKTAG('a','v','c','1');
> > +    else if (track->par->codec_id == AV_CODEC_ID_HEVC)      tag =
> MKTAG('h','e','v','1');
> > +    else if (track->par->codec_id == AV_CODEC_ID_VP9)       tag =
> MKTAG('v','p','0','9');
> > +    else if (track->par->codec_id == AV_CODEC_ID_AC3)       tag =
> MKTAG('a','c','-','3');
> > +    else if (track->par->codec_id == AV_CODEC_ID_EAC3)      tag =
> MKTAG('e','c','-','3');
> > +    else if (track->par->codec_id == AV_CODEC_ID_DIRAC)     tag =
> MKTAG('d','r','a','c');
> > +    else if (track->par->codec_id == AV_CODEC_ID_MOV_TEXT)  tag =
> MKTAG('t','x','3','g');
> > +    else if (track->par->codec_id == AV_CODEC_ID_VC1)       tag =
> MKTAG('v','c','-','1');
> > +    else if (track->par->codec_id == AV_CODEC_ID_FLAC)      tag =
> MKTAG('f','L','a','C');
> > +    else if (track->par->codec_id == AV_CODEC_ID_OPUS)      tag =
> MKTAG('O','p','u','s');
> > +    else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO)  tag =
> MKTAG('m','p','4','v');
> > +    else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO)  tag =
> MKTAG('m','p','4','a');
> > +    else if (track->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE)  tag =
> MKTAG('m','p','4','s');
> > +
> > +    return tag;
> > +}
> > +
> > +static const AVCodecTag codec_ipod_tags[] = {
> > +    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> > +    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> > +    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> > +    { AV_CODEC_ID_ALAC,     MKTAG('a','l','a','c') },
> > +    { AV_CODEC_ID_AC3,      MKTAG('a','c','-','3') },
> > +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> > +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') },
> > +    { AV_CODEC_ID_NONE, 0 },
> > +};
> > +
> > +static int ipod_get_codec_tag(AVFormatContext *s, MOVTrack *track)
> > +{
> > +    int tag = track->par->codec_tag;
> > +
> > +    // keep original tag for subs, ipod supports both formats
> > +    if (!(track->par->codec_type == AVMEDIA_TYPE_SUBTITLE &&
> > +          (tag == MKTAG('t', 'x', '3', 'g') ||
> > +           tag == MKTAG('t', 'e', 'x', 't'))))
> > +        tag = ff_codec_get_tag(codec_ipod_tags, track->par->codec_id);
> > +
> > +    if (!av_match_ext(s->filename, "m4a") &&
> > +        !av_match_ext(s->filename, "m4b") &&
> > +        !av_match_ext(s->filename, "m4v"))
> > +        av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a, .m4v
> nor  .m4b "
> > +               "Quicktime/Ipod might not play the file\n");
> > +
> > +    return tag;
> > +}
> > +
> >  static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track)
> >  {
> >      int tag;
> > @@ -1506,25 +1555,42 @@ static int mov_get_codec_tag(AVFormatContext
> *s, MOVTrack *track)
> >      return tag;
> >  }
> >
> > +static const AVCodecTag codec_3gp_tags[] = {
> > +    { AV_CODEC_ID_H263,     MKTAG('s','2','6','3') },
> > +    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> > +    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> > +    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> > +    { AV_CODEC_ID_AMR_NB,   MKTAG('s','a','m','r') },
> > +    { AV_CODEC_ID_AMR_WB,   MKTAG('s','a','w','b') },
> > +    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> > +    { AV_CODEC_ID_NONE, 0 },
> > +};
> > +
> > +static const AVCodecTag codec_f4v_tags[] = { // XXX: add GIF/PNG/JPEG?
> > +    { AV_CODEC_ID_MP3,    MKTAG('.','m','p','3') },
> > +    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
> > +    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
> > +    { AV_CODEC_ID_VP6A,   MKTAG('V','P','6','A') },
> > +    { AV_CODEC_ID_VP6F,   MKTAG('V','P','6','F') },
> > +    { AV_CODEC_ID_NONE, 0 },
> > +};
> > +
> >  static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track)
> >  {
> >      int tag;
> >
> >      if (track->mode == MODE_MP4 || track->mode == MODE_PSP)
> > -        tag = track->par->codec_tag;
> > -    else if (track->mode == MODE_ISM)
> > -        tag = track->par->codec_tag;
> > -    else if (track->mode == MODE_IPOD) {
> > -        if (!av_match_ext(s->filename, "m4a") &&
> > -            !av_match_ext(s->filename, "m4v") &&
> > -            !av_match_ext(s->filename, "m4b"))
> > -            av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a
> nor .m4v "
> > -                   "Quicktime/Ipod might not play the file\n");
> > -        tag = track->par->codec_tag;
> > -    } else if (track->mode & MODE_3GP)
> > -        tag = track->par->codec_tag;
> > +        tag = mp4_get_codec_tag(s, track);
> > +    else if (track->mode == MODE_ISM) {
> > +        tag = mp4_get_codec_tag(s, track);
> > +        if (!tag && track->par->codec_id == AV_CODEC_ID_WMAPRO)
> > +            tag = MKTAG('w', 'm', 'a', ' ');
> > +    } else if (track->mode == MODE_IPOD)
> > +        tag = ipod_get_codec_tag(s, track);
> > +    else if (track->mode & MODE_3GP)
> > +        tag = ff_codec_get_tag(codec_3gp_tags, track->par->codec_id);
> >      else if (track->mode == MODE_F4V)
> > -        tag = track->par->codec_tag;
> > +        tag = ff_codec_get_tag(codec_f4v_tags, track->par->codec_id);
> >      else
> >          tag = mov_get_codec_tag(s, track);
> >
> > @@ -2060,6 +2126,19 @@ static int mov_write_tmcd_tag(AVIOContext *pb,
> MOVTrack *track)
> >      return update_size(pb, pos);
> >  }
> >
> > +static int mov_write_camm_tag(AVIOContext *pb) {
> > +    int64_t size_update;
> > +    int64_t pos = avio_tell(pb);
> > +    avio_wb32(pb, 0); /* size */
> > +    ffio_wfourcc(pb, "camm");
> > +    avio_wb32(pb, 0); /* Reserved */
> > +    avio_wb16(pb, 0); /* Reserved */
> > +    avio_wb16(pb, 1); /* Data-reference index */
> > +
> > +    size_update = update_size(pb, pos);
> > +    return size_update;
> > +}
> > +
> >  static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb,
> MOVMuxContext *mov, MOVTrack *track)
> >  {
> >      int64_t pos = avio_tell(pb);
> > @@ -2077,6 +2156,10 @@ static int mov_write_stsd_tag(AVFormatContext
> *s, AVIOContext *pb, MOVMuxContext
> >          mov_write_rtp_tag(pb, track);
> >      else if (track->par->codec_tag == MKTAG('t','m','c','d'))
> >          mov_write_tmcd_tag(pb, track);
> > +    else if (track->par->codec_tag
> > +             == ff_codec_get_tag(ff_mp4_obj_type,
> > +                                 AV_CODEC_ID_CAMERA_MOTION_METADATA))
> > +        mov_write_camm_tag(pb);
> >      return update_size(pb, pos);
> >  }
> >
> > @@ -2443,6 +2526,11 @@ static int mov_write_hdlr_tag(AVFormatContext
> *s, AVIOContext *pb, MOVTrack *tra
> >          } else if (track->par->codec_tag == MKTAG('t','m','c','d')) {
> >              hdlr_type = "tmcd";
> >              descr = "TimeCodeHandler";
> > +        } else if (track->par->codec_tag
> > +                       == ff_codec_get_tag(ff_mp4_obj_type,
> > +                            AV_CODEC_ID_CAMERA_MOTION_METADATA)) {
> > +            hdlr_type = "camm";
> > +            descr = "CameraMetadataMotionHandler";
> >          } else {
> >              av_log(s, AV_LOG_WARNING,
> >                     "Unknown hldr_type for %s, writing dummy values\n",
> > @@ -6422,73 +6510,6 @@ static int mov_check_bitstream(struct
> AVFormatContext *s, const AVPacket *pkt)
> >      return ret;
> >  }
> >
> > -static const AVCodecTag codec_3gp_tags[] = {
> > -    { AV_CODEC_ID_H263,     MKTAG('s','2','6','3') },
> > -    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> > -    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> > -    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> > -    { AV_CODEC_ID_AMR_NB,   MKTAG('s','a','m','r') },
> > -    { AV_CODEC_ID_AMR_WB,   MKTAG('s','a','w','b') },
> > -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> > -    { AV_CODEC_ID_NONE, 0 },
> > -};
> > -
> > -const AVCodecTag codec_mp4_tags[] = {
> > -    { AV_CODEC_ID_MPEG4       , MKTAG('m', 'p', '4', 'v') },
> > -    { AV_CODEC_ID_H264        , MKTAG('a', 'v', 'c', '1') },
> > -    { AV_CODEC_ID_HEVC        , MKTAG('h', 'e', 'v', '1') },
> > -    { AV_CODEC_ID_HEVC        , MKTAG('h', 'v', 'c', '1') },
> > -    { AV_CODEC_ID_MPEG2VIDEO  , MKTAG('m', 'p', '4', 'v') },
> > -    { AV_CODEC_ID_MPEG1VIDEO  , MKTAG('m', 'p', '4', 'v') },
> > -    { AV_CODEC_ID_MJPEG       , MKTAG('m', 'p', '4', 'v') },
> > -    { AV_CODEC_ID_PNG         , MKTAG('m', 'p', '4', 'v') },
> > -    { AV_CODEC_ID_JPEG2000    , MKTAG('m', 'p', '4', 'v') },
> > -    { AV_CODEC_ID_VC1         , MKTAG('v', 'c', '-', '1') },
> > -    { AV_CODEC_ID_DIRAC       , MKTAG('d', 'r', 'a', 'c') },
> > -    { AV_CODEC_ID_TSCC2       , MKTAG('m', 'p', '4', 'v') },
> > -    { AV_CODEC_ID_VP9         , MKTAG('v', 'p', '0', '9') },
> > -    { AV_CODEC_ID_AAC         , MKTAG('m', 'p', '4', 'a') },
> > -    { AV_CODEC_ID_MP4ALS      , MKTAG('m', 'p', '4', 'a') },
> > -    { AV_CODEC_ID_MP3         , MKTAG('m', 'p', '4', 'a') },
> > -    { AV_CODEC_ID_MP2         , MKTAG('m', 'p', '4', 'a') },
> > -    { AV_CODEC_ID_AC3         , MKTAG('a', 'c', '-', '3') },
> > -    { AV_CODEC_ID_EAC3        , MKTAG('e', 'c', '-', '3') },
> > -    { AV_CODEC_ID_DTS         , MKTAG('m', 'p', '4', 'a') },
> > -    { AV_CODEC_ID_FLAC        , MKTAG('f', 'L', 'a', 'C') },
> > -    { AV_CODEC_ID_OPUS        , MKTAG('O', 'p', 'u', 's') },
> > -    { AV_CODEC_ID_VORBIS      , MKTAG('m', 'p', '4', 'a') },
> > -    { AV_CODEC_ID_QCELP       , MKTAG('m', 'p', '4', 'a') },
> > -    { AV_CODEC_ID_EVRC        , MKTAG('m', 'p', '4', 'a') },
> > -    { AV_CODEC_ID_DVD_SUBTITLE, MKTAG('m', 'p', '4', 's') },
> > -    { AV_CODEC_ID_MOV_TEXT    , MKTAG('t', 'x', '3', 'g') },
> > -    { AV_CODEC_ID_NONE        ,    0 },
> > -};
> > -
> > -const AVCodecTag codec_ism_tags[] = {
> > -    { AV_CODEC_ID_WMAPRO      , MKTAG('w', 'm', 'a', ' ') },
> > -    { AV_CODEC_ID_NONE        ,    0 },
> > -};
> > -
> > -static const AVCodecTag codec_ipod_tags[] = {
> > -    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
> > -    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
> > -    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
> > -    { AV_CODEC_ID_ALAC,     MKTAG('a','l','a','c') },
> > -    { AV_CODEC_ID_AC3,      MKTAG('a','c','-','3') },
> > -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
> > -    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') },
> > -    { AV_CODEC_ID_NONE, 0 },
> > -};
> > -
> > -static const AVCodecTag codec_f4v_tags[] = {
> > -    { AV_CODEC_ID_MP3,    MKTAG('.','m','p','3') },
> > -    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
> > -    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
> > -    { AV_CODEC_ID_VP6A,   MKTAG('V','P','6','A') },
> > -    { AV_CODEC_ID_VP6F,   MKTAG('V','P','6','F') },
> > -    { AV_CODEC_ID_NONE, 0 },
> > -};
> > -
> >  #if CONFIG_MOV_MUXER
> >  MOV_CLASS(mov)
> >  AVOutputFormat ff_mov_muxer = {
> > @@ -6549,7 +6570,7 @@ AVOutputFormat ff_mp4_muxer = {
> >      .write_trailer     = mov_write_trailer,
> >      .deinit            = mov_free,
> >      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH |
> AVFMT_TS_NEGATIVE,
> > -    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags,
> 0 },
> > +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type,
> 0 },
> >      .check_bitstream   = mov_check_bitstream,
> >      .priv_class        = &mp4_muxer_class,
> >  };
> > @@ -6570,7 +6591,7 @@ AVOutputFormat ff_psp_muxer = {
> >      .write_trailer     = mov_write_trailer,
> >      .deinit            = mov_free,
> >      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH |
> AVFMT_TS_NEGATIVE,
> > -    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags,
> 0 },
> > +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type,
> 0 },
> >      .check_bitstream   = mov_check_bitstream,
> >      .priv_class        = &psp_muxer_class,
> >  };
> > @@ -6632,8 +6653,7 @@ AVOutputFormat ff_ismv_muxer = {
> >      .write_trailer     = mov_write_trailer,
> >      .deinit            = mov_free,
> >      .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH |
> AVFMT_TS_NEGATIVE,
> > -    .codec_tag         = (const AVCodecTag* const []){
> > -        codec_mp4_tags, codec_ism_tags, 0 },
> > +    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type,
> 0 },
> >      .check_bitstream   = mov_check_bitstream,
> >      .priv_class        = &ismv_muxer_class,
> >  };
>
> This stuff apparently got in by mistake?
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
Hendrik Leppkes July 7, 2017, 10:47 p.m. UTC | #6
On Sat, Jul 8, 2017 at 12:24 AM, Louis O'Bryan
<louiso-at-google.com@ffmpeg.org> wrote:
>
> The use case is to write data to a new stream in the mp4 container. The
> encoder isn't changing the data. This data would reside in the same mp4
> container as video and audio streams. Are you suggesting there is a way I
> can accomplish that task without creating this encoder (and if so, how)?

You don't need an encoder to create AVPackets, you can just do that
yourself, even more so if the encoder apparently doesn't do any
encoding.
So why does it need encoding in the first place? Why can't you just
pass the finished data in an AVPacket to the mov muxer? Thats how data
streams are supposed to work.

>
>> +static av_cold int encode_init(AVCodecContext *avctx) {
>> > +    // Use dummy values for the height and width.
>> > +    avctx->width = DUMMY_ENCODER_SIZE;
>> > +    avctx->height = DUMMY_ENCODER_SIZE;
>> > +    avctx->max_pixels = DUMMY_ENCODER_SIZE;
>> What? This makes no sense.
>
> Using avcodec_encode_video2() seems to require that the width and height be
> nonzero. What is the recommended way to avoid that?
>

Clearly you should not use a video encoder to encode something thats
not a video. This entire approach is not acceptable.
Louis O'Bryan July 7, 2017, 11:23 p.m. UTC | #7
It sounds like my code should be outside an encoder then. I think this
stems from my misunderstanding of how the API can be used. I will rewrite
my code that muxes streams to use an AVPacket directly instead.

On Fri, Jul 7, 2017 at 3:47 PM, Hendrik Leppkes <h.leppkes@gmail.com> wrote:

> On Sat, Jul 8, 2017 at 12:24 AM, Louis O'Bryan
> <louiso-at-google.com@ffmpeg.org> wrote:
> >
> > The use case is to write data to a new stream in the mp4 container. The
> > encoder isn't changing the data. This data would reside in the same mp4
> > container as video and audio streams. Are you suggesting there is a way I
> > can accomplish that task without creating this encoder (and if so, how)?
>
> You don't need an encoder to create AVPackets, you can just do that
> yourself, even more so if the encoder apparently doesn't do any
> encoding.
> So why does it need encoding in the first place? Why can't you just
> pass the finished data in an AVPacket to the mov muxer? Thats how data
> streams are supposed to work.
>
> >
> >> +static av_cold int encode_init(AVCodecContext *avctx) {
> >> > +    // Use dummy values for the height and width.
> >> > +    avctx->width = DUMMY_ENCODER_SIZE;
> >> > +    avctx->height = DUMMY_ENCODER_SIZE;
> >> > +    avctx->max_pixels = DUMMY_ENCODER_SIZE;
> >> What? This makes no sense.
> >
> > Using avcodec_encode_video2() seems to require that the width and height
> be
> > nonzero. What is the recommended way to avoid that?
> >
>
> Clearly you should not use a video encoder to encode something thats
> not a video. This entire approach is not acceptable.
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
wm4 July 10, 2017, 8:48 a.m. UTC | #8
On Fri, 7 Jul 2017 15:24:02 -0700
"Louis O'Bryan" <louiso-at-google.com@ffmpeg.org> wrote:

> > +static av_cold int encode_init(AVCodecContext *avctx) {  
> > > +    // Use dummy values for the height and width.
> > > +    avctx->width = DUMMY_ENCODER_SIZE;
> > > +    avctx->height = DUMMY_ENCODER_SIZE;
> > > +    avctx->max_pixels = DUMMY_ENCODER_SIZE;  
> > What? This makes no sense.  
> 
> Using avcodec_encode_video2() seems to require that the width and height be
> nonzero. What is the recommended way to avoid that?

Well, that API is for video. So I guess it's natural that it errors out
if some basic parameters that would be necessarily always required for
video are not set. There are other APIs for audio and subtitles. Also,
these API functions are deprecated.
Louis O'Bryan July 11, 2017, 11:17 p.m. UTC | #9
If I need to write a new atom under stsd for my stream in the mov muxer
<https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/movenc.c>
(mov_write_stsd_tag),
is it appropriate to indicate that through the AVStream metadata rather
than the codec_tag?

On Mon, Jul 10, 2017 at 1:48 AM, wm4 <nfxjfg@googlemail.com> wrote:

> On Fri, 7 Jul 2017 15:24:02 -0700
> "Louis O'Bryan" <louiso-at-google.com@ffmpeg.org> wrote:
>
> > > +static av_cold int encode_init(AVCodecContext *avctx) {
> > > > +    // Use dummy values for the height and width.
> > > > +    avctx->width = DUMMY_ENCODER_SIZE;
> > > > +    avctx->height = DUMMY_ENCODER_SIZE;
> > > > +    avctx->max_pixels = DUMMY_ENCODER_SIZE;
> > > What? This makes no sense.
> >
> > Using avcodec_encode_video2() seems to require that the width and height
> be
> > nonzero. What is the recommended way to avoid that?
>
> Well, that API is for video. So I guess it's natural that it errors out
> if some basic parameters that would be necessarily always required for
> video are not set. There are other APIs for audio and subtitles. Also,
> these API functions are deprecated.
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
wm4 July 12, 2017, 7:50 a.m. UTC | #10
On Tue, 11 Jul 2017 16:17:33 -0700
"Louis O'Bryan" <louiso-at-google.com@ffmpeg.org> wrote:

> If I need to write a new atom under stsd for my stream in the mov muxer
> <https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/movenc.c>
> (mov_write_stsd_tag),
> is it appropriate to indicate that through the AVStream metadata rather
> than the codec_tag?

It seemed to have lots of unrelated changes, but maybe I'm missing
something. If those codec tag refactors are needed, they should
probably be split into a separate patch.

But it looks like most of those changes were unintended (Moritz
suspected that too). The tag addition itself is probably fine.

Also, please don't top post on mailing lists.
Louis O'Bryan July 12, 2017, 4:16 p.m. UTC | #11
On Wed, Jul 12, 2017 at 12:50 AM, wm4 <nfxjfg@googlemail.com> wrote:

> On Tue, 11 Jul 2017 16:17:33 -0700
> "Louis O'Bryan" <louiso-at-google.com@ffmpeg.org> wrote:
>
> > If I need to write a new atom under stsd for my stream in the mov muxer
> > <https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/movenc.c>
> > (mov_write_stsd_tag),
> > is it appropriate to indicate that through the AVStream metadata rather
> > than the codec_tag?
>
> It seemed to have lots of unrelated changes, but maybe I'm missing
> something. If those codec tag refactors are needed, they should
> probably be split into a separate patch.
>
> But it looks like most of those changes were unintended (Moritz
> suspected that too). The tag addition itself is probably fine.
>
> Also, please don't top post on mailing lists.
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>

That file had unrelated changes that shouldn't have been there, please
ignore them.
Now that there is no codec associated with the stream, there shouldn't be a
codec tag at all, I would assume. (Another issue I need to deal with is
that the MOV muxer also doesn't support streams without a codec, but that
is separate.)
diff mbox

Patch

diff --git a/Changelog b/Changelog
index a8726c6736..5f98385b53 100644
--- a/Changelog
+++ b/Changelog
@@ -2,6 +2,7 @@  Entries are sorted chronologically from oldest to youngest within each release,
 releases are sorted from youngest to oldest.
 
 version <next>:
+- Camera metadata motion encoder
 - deflicker video filter
 - doubleweave video filter
 - lumakey video filter
diff --git a/doc/general.texi b/doc/general.texi
index 8f582d586f..06996c81e8 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -912,6 +912,8 @@  following image formats are supported:
     @tab part of LCL, encoder experimental
 @item Zip Motion Blocks Video  @tab   X @tab  X
     @tab Encoder works only in PAL8.
+@item Camera metadata motion    @tab X  @tab
+    @tab Encoder for camera sensor data.
 @end multitable
 
 @code{X} means that encoding (resp. decoding) is supported.
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index b440a00746..306cc793ee 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -680,6 +680,7 @@  OBJS-$(CONFIG_ZLIB_DECODER)            += lcldec.o
 OBJS-$(CONFIG_ZLIB_ENCODER)            += lclenc.o
 OBJS-$(CONFIG_ZMBV_DECODER)            += zmbv.o
 OBJS-$(CONFIG_ZMBV_ENCODER)            += zmbvenc.o
+OBJS-$(CONFIG_CAMERA_MOTION_METADATA_ENCODER) += cammenc.o
 
 # (AD)PCM decoders/encoders
 OBJS-$(CONFIG_PCM_ALAW_DECODER)           += pcm.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 0243f47358..eff51f4042 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -651,6 +651,9 @@  static void register_all(void)
     REGISTER_DECODER(XBIN,              xbin);
     REGISTER_DECODER(IDF,               idf);
 
+    /* data */
+    REGISTER_ENCODER(CAMERA_MOTION_METADATA, camera_motion_metadata);
+
     /* external libraries, that shouldn't be used by default if one of the
      * above is available */
     REGISTER_ENCDEC (LIBOPENH264,       libopenh264);
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index b697afa0ae..622383f453 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -681,6 +681,7 @@  enum AVCodecID {
     AV_CODEC_ID_DVD_NAV,
     AV_CODEC_ID_TIMED_ID3,
     AV_CODEC_ID_BIN_DATA,
+    AV_CODEC_ID_CAMERA_MOTION_METADATA,
 
 
     AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like AV_CODEC_ID_NONE) but lavf should attempt to identify it
diff --git a/libavcodec/cammenc.c b/libavcodec/cammenc.c
new file mode 100644
index 0000000000..d7592ab69d
--- /dev/null
+++ b/libavcodec/cammenc.c
@@ -0,0 +1,299 @@ 
+/*
+ * Reference implementation for the CAMM Metadata encoder.
+ * Encodes sensor data for 360-degree cameras such as
+ * GPS, gyro, and acceleration. This is stored in a track separate from video
+ * and audio.
+ *
+ * Copyright (c) 2017 Louis O'Bryan
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * CAMM Metadata encoder
+ * @author Louis O'Bryan
+ */
+
+#include <math.h>
+#include <float.h>
+#include "avcodec.h"
+#include "internal.h"
+#include "bytestream.h"
+#include "libavutil/common.h"
+#include "libavutil/opt.h"
+
+#define DUMMY_ENCODER_SIZE 1
+
+typedef struct CammContext {
+    AVCodecContext *avctx;
+} CammContext;
+
+// Sizes of each type of metadata.
+static int metadata_type_sizes[] = {
+  3 * sizeof(float),
+  2 * sizeof(uint64_t),
+  3 * sizeof(float),
+  3 * sizeof(float),
+  3 * sizeof(float),
+  3 * sizeof(float),
+  3 * sizeof(double) + sizeof(uint32_t) + 7 * sizeof(float),
+  3 * sizeof(float)
+};
+
+static int min_packet_size = sizeof(uint16_t) * 2;
+
+/**
+ * Validates that the latitude has a valid value. Returns 1 if ok, else 0.
+ */
+static int validate_latitude(AVCodecContext *avctx, double latitude) {
+    if (latitude < -M_PI / 4 || latitude > M_PI / 4) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Latitude %f is not in bounds (%f, %f)\n",
+               latitude, -M_PI / 4, M_PI / 4);
+        return 0;
+    }
+    return 1;
+}
+
+/**
+ * Validates that the longitude has a valid value. Returns 1 if ok, else 0.
+ */
+static int validate_longitude(AVCodecContext *avctx, double longitude) {
+    if (longitude < -M_PI / 2 || longitude > M_PI / 2) {
+        av_log(avctx, AV_LOG_ERROR,
+               "longitude %f is not in bounds (%f, %f)\n",
+               longitude, -M_PI / 2, M_PI / 2);
+        return 0;
+    }
+    return 1;
+}
+
+static void log_float_values(AVCodecContext *avctx, const char *name, float *data) {
+    int i;
+    float value;
+    for (i = 0; i < 3; ++i) {
+        value = data[i];
+        av_log(avctx, AV_LOG_DEBUG, "%s[%d] = %f\n", name, i, value);
+    }
+}
+
+/**
+ * Validates that the incoming data is correctly formatted and returns
+ * 1 if it is, 0 otherwise.
+ */
+static int validate_data(AVCodecContext *avctx, const AVFrame *data) {
+    uint16_t packet_type;
+    int expected_packet_size;
+    uint64_t pixel_exposure_time;
+    uint64_t rolling_shutter_skew_time;
+    uint32_t *camm_data;
+    double time_gps_epoch;
+    uint32_t gps_fix_type;
+    double latitude;
+    double longitude;
+    float altitude;
+
+    if (data->pkt_size < min_packet_size) {
+        av_log(avctx, AV_LOG_ERROR,
+               "CAMM input data with size %d is too small\n", data->pkt_size);
+        return 0;
+    }
+
+    packet_type = ((uint16_t*)(*data->extended_data))[1];
+    if (packet_type > sizeof(metadata_type_sizes) / sizeof(int)) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Packet type %d is not recognized\n", packet_type);
+        return 0;
+    }
+
+    expected_packet_size = metadata_type_sizes[packet_type];
+    if (expected_packet_size != data->pkt_size) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Packet size %d does not match expected size %d for type %d\n",
+               data->pkt_size, expected_packet_size, packet_type);
+        return 0;
+    }
+
+    // The actual sensor data starts after the reserved slot and packet type.
+    camm_data = ((uint32_t*)(*data->extended_data)) + 1;
+    switch (packet_type) {
+        // float angle_axis[3]
+        case 0:
+            log_float_values(avctx, "angle_axis", (float*)camm_data);
+            break;
+        // int32 pixel_exposure_time
+        // int32 rolling_shutter_skew_time
+        case 1:
+            pixel_exposure_time = ((uint32_t*)camm_data)[0];
+            av_log(avctx, AV_LOG_DEBUG,
+                   "pixel_exposure_time = %lu\n", pixel_exposure_time);
+            rolling_shutter_skew_time = ((uint32_t*)camm_data)[1];
+            av_log(avctx, AV_LOG_DEBUG, "rolling_shutter_skew_time = %lu\n",
+                   rolling_shutter_skew_time);
+            break;
+        // float gyro[3]
+        case 2:
+            log_float_values(avctx, "gyro", (float*)camm_data);
+            break;
+        case 3:
+            log_float_values(avctx, "acceleration", (float*)camm_data);
+            break;
+        // float position[3]
+        case 4:
+            log_float_values(avctx, "position", (float*)camm_data);
+            break;
+        // float latitude
+        // float longitude
+        // float altitude
+        case 5:
+            if (!validate_latitude(avctx, ((float*)camm_data)[0])) return 0;
+            if (!validate_longitude(avctx, ((float*)camm_data)[1])) return 0;
+
+            av_log(avctx, AV_LOG_DEBUG, "latitude = %.6f\n",
+                   ((float*)camm_data)[0]);
+            av_log(avctx, AV_LOG_DEBUG, "longitude = %.6f\n",
+                   ((float*)camm_data)[1]);
+            av_log(avctx, AV_LOG_DEBUG, "altitude = %.6f\n",
+                   ((float*)camm_data)[2]);
+            break;
+        case 6:
+            time_gps_epoch = ((double*)camm_data)[0];
+            if (time_gps_epoch < 0) {
+                av_log(avctx, AV_LOG_ERROR,
+                       "Time gps epoch %.6f is less than 0.\n",
+                       time_gps_epoch);
+                return 0;
+            }
+            av_log(avctx, AV_LOG_DEBUG, "time_gps_epoch = %.6f\n",
+                   time_gps_epoch);
+            camm_data = (uint32_t*) (((double*)camm_data) + 1);
+
+            gps_fix_type = ((uint32_t*)camm_data)[0];
+            if (gps_fix_type != 0 && gps_fix_type != 1 && gps_fix_type != 2) {
+                av_log(avctx, AV_LOG_ERROR,
+                       "GPS fix type %d is not valid. Should be 0, 1, or 2\n",
+                       gps_fix_type);
+                return 0;
+            }
+            av_log(avctx, AV_LOG_DEBUG, "gps_fix_type = %d\n", gps_fix_type);
+            camm_data = (uint32_t*) (((int32_t*)camm_data) + 1);
+
+            latitude = ((double*)camm_data)[0];
+            if (!validate_latitude(avctx, latitude)) return 0;
+            av_log(avctx, AV_LOG_DEBUG, "latitude = %.6f\n", latitude);
+            camm_data = (uint32_t*) (((double*)camm_data) + 1);
+
+            longitude = ((double*)camm_data)[0];
+            if (!validate_longitude(avctx, longitude)) return 0;
+            av_log(avctx, AV_LOG_DEBUG, "longitude = %.6f\n", latitude);
+            camm_data = (uint32_t*) (((double*)camm_data) + 1);
+
+            altitude = ((float*)camm_data)[0];
+            av_log(avctx, AV_LOG_DEBUG, "altitude = %.6f\n", altitude);
+            camm_data = (uint32_t*) (((float*)camm_data) + 1);
+
+            av_log(avctx, AV_LOG_DEBUG, "horizontal accuracy = %.6f\n",
+                   ((float*)camm_data)[0]);
+            camm_data = (uint32_t*) (((float*)camm_data) + 1);
+
+            av_log(avctx, AV_LOG_DEBUG, "vertical accuracy %.6f\n",
+                   ((float*)camm_data)[0]);
+            camm_data = (uint32_t*) (((float*)camm_data) + 1);
+
+            av_log(avctx, AV_LOG_DEBUG, "vertical east %.6f\n",
+                   ((float*)camm_data)[0]);
+            camm_data = (uint32_t*) (((float*)camm_data) + 1);
+
+            av_log(avctx, AV_LOG_DEBUG, "vertical north %.6f\n",
+                   ((float*)camm_data)[0]);
+            camm_data = (uint32_t*) (((float*)camm_data) + 1);
+
+            av_log(avctx, AV_LOG_DEBUG, "vertical up %.6f\n",
+                   ((float*)camm_data)[0]);
+            camm_data = (uint32_t*) (((float*)camm_data) + 1);
+
+            av_log(avctx, AV_LOG_DEBUG, "speed accuracy %.6f\n",
+                   ((float*)camm_data)[0]);
+            camm_data = (uint32_t*) (((float*)camm_data) + 1);
+            break;
+        // float magnetic_field[3]
+        case 7:
+            log_float_values(avctx, "magnetic_field", (float*)camm_data);
+            break;
+    }
+
+    return 1;
+}
+
+/**
+ * Encode camera motion metadata. Essentially, this encoder validates the
+ * input data and then just copies the data to an output AVPacket.
+ *  @param avctx          The AVCodecContext context.
+ *  @param packet         The output packet to write the encoded data.
+ *  @param data           The input camera sensor data.
+ *  @param got_packet     1 if a non-empty packet was returned, 0 otherwise.
+ */
+static int encode_data(AVCodecContext *avctx, AVPacket *packet,
+                        const AVFrame *data, int *got_packet)
+{
+    int ret;
+
+    if (!validate_data(avctx, data)) {
+        return AVERROR(EINVAL);
+    }
+
+    // Make sure there is enough size allocated in packet->data.
+    if ((ret = ff_alloc_packet2(avctx, packet, data->pkt_size, 0)) < 0) {
+        av_log(avctx, AV_LOG_ERROR, "Can't allocate %d bytes in packet\n",
+               data->pkt_size);
+        return ret;
+    }
+
+    // Copy necessary fields to the AVPacket.
+    packet->pts = data->pts;
+    packet->duration = data->pkt_duration;
+    memcpy(packet->data, *(data->extended_data), data->pkt_size);
+
+    // Indicate that a packet was created.
+    *got_packet = 1;
+    return 0;
+}
+
+/**
+ * Initializes the codec.
+ *
+ * @param avctx     The AVCodecContext context.
+ */
+static av_cold int encode_init(AVCodecContext *avctx) {
+    // Use dummy values for the height and width.
+    avctx->width = DUMMY_ENCODER_SIZE;
+    avctx->height = DUMMY_ENCODER_SIZE;
+    avctx->max_pixels = DUMMY_ENCODER_SIZE;
+
+    return 0;
+}
+
+// Define the encoder that ffmpeg will use for CAMM data.
+AVCodec ff_camera_motion_metadata_encoder = {
+    .name           = "camm",
+    .long_name      = NULL_IF_CONFIG_SMALL("camera motion metadata"),
+    .type           = AVMEDIA_TYPE_DATA,
+    .id             = AV_CODEC_ID_CAMERA_MOTION_METADATA,
+    .priv_data_size = sizeof(CammContext),
+    .encode2        = encode_data,
+    .init           = encode_init,
+};
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index cf1246e431..014c1cd6b3 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -3100,6 +3100,12 @@  static const AVCodecDescriptor codec_descriptors[] = {
         .name      = "scte_35",
         .long_name = NULL_IF_CONFIG_SMALL("SCTE 35 Message Queue"),
     },
+    {
+        .id        = AV_CODEC_ID_CAMERA_MOTION_METADATA,
+        .type      = AVMEDIA_TYPE_DATA,
+        .name      = "camm",
+        .long_name = NULL_IF_CONFIG_SMALL("camera motion metadata"),
+    },
 
     /* deprecated codec ids */
 };
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 3c5fea9327..5b99785a72 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -28,8 +28,8 @@ 
 #include "libavutil/version.h"
 
 #define LIBAVCODEC_VERSION_MAJOR  57
-#define LIBAVCODEC_VERSION_MINOR 100
-#define LIBAVCODEC_VERSION_MICRO 103
+#define LIBAVCODEC_VERSION_MINOR 101
+#define LIBAVCODEC_VERSION_MICRO 100
 
 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
                                                LIBAVCODEC_VERSION_MINOR, \
diff --git a/libavformat/isom.c b/libavformat/isom.c
index 3a9b3baf96..ca5107bf7f 100644
--- a/libavformat/isom.c
+++ b/libavformat/isom.c
@@ -67,6 +67,7 @@  const AVCodecTag ff_mp4_obj_type[] = {
     { AV_CODEC_ID_VORBIS      , 0xDD }, /* nonstandard, gpac uses it */
     { AV_CODEC_ID_DVD_SUBTITLE, 0xE0 }, /* nonstandard, see unsupported-embedded-subs-2.mp4 */
     { AV_CODEC_ID_QCELP       , 0xE1 },
+    { AV_CODEC_ID_CAMERA_MOTION_METADATA, 0xE2 },
     { AV_CODEC_ID_MPEG4SYSTEMS, 0x01 },
     { AV_CODEC_ID_MPEG4SYSTEMS, 0x02 },
     { AV_CODEC_ID_NONE        ,    0 },
@@ -368,6 +369,11 @@  const AVCodecTag ff_codec_movsubtitle_tags[] = {
     { AV_CODEC_ID_NONE, 0 },
 };
 
+const AVCodecTag ff_codec_movdata_tags[] = {
+    { AV_CODEC_ID_CAMERA_MOTION_METADATA, MKTAG('c','a','m','m') },
+};
+
+
 /* map numeric codes from mdhd atom to ISO 639 */
 /* cf. QTFileFormat.pdf p253, qtff.pdf p205 */
 /* http://developer.apple.com/documentation/mac/Text/Text-368.html */
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 88f2f2c819..49528f8635 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -35,8 +35,6 @@ 
 #include "libavcodec/dnxhddata.h"
 #include "libavcodec/flac.h"
 #include "libavcodec/get_bits.h"
-
-#include "libavcodec/internal.h"
 #include "libavcodec/put_bits.h"
 #include "libavcodec/vc1_common.h"
 #include "libavcodec/raw.h"
@@ -1126,10 +1124,7 @@  static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track)
 
     avio_wb32(pb, 0);
     ffio_wfourcc(pb, "hvcC");
-    if (track->tag == MKTAG('h','v','c','1'))
-        ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 1);
-    else
-        ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
+    ff_isom_write_hvcc(pb, track->vos_data, track->vos_len, 0);
     return update_size(pb, pos);
 }
 
@@ -1230,6 +1225,60 @@  static int mov_write_dpxe_tag(AVIOContext *pb, MOVTrack *track)
     return 0;
 }
 
+static int mp4_get_codec_tag(AVFormatContext *s, MOVTrack *track)
+{
+    int tag = track->par->codec_tag;
+
+    if (!ff_codec_get_tag(ff_mp4_obj_type, track->par->codec_id))
+        return 0;
+
+    if      (track->par->codec_id == AV_CODEC_ID_H264)      tag = MKTAG('a','v','c','1');
+    else if (track->par->codec_id == AV_CODEC_ID_HEVC)      tag = MKTAG('h','e','v','1');
+    else if (track->par->codec_id == AV_CODEC_ID_VP9)       tag = MKTAG('v','p','0','9');
+    else if (track->par->codec_id == AV_CODEC_ID_AC3)       tag = MKTAG('a','c','-','3');
+    else if (track->par->codec_id == AV_CODEC_ID_EAC3)      tag = MKTAG('e','c','-','3');
+    else if (track->par->codec_id == AV_CODEC_ID_DIRAC)     tag = MKTAG('d','r','a','c');
+    else if (track->par->codec_id == AV_CODEC_ID_MOV_TEXT)  tag = MKTAG('t','x','3','g');
+    else if (track->par->codec_id == AV_CODEC_ID_VC1)       tag = MKTAG('v','c','-','1');
+    else if (track->par->codec_id == AV_CODEC_ID_FLAC)      tag = MKTAG('f','L','a','C');
+    else if (track->par->codec_id == AV_CODEC_ID_OPUS)      tag = MKTAG('O','p','u','s');
+    else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO)  tag = MKTAG('m','p','4','v');
+    else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO)  tag = MKTAG('m','p','4','a');
+    else if (track->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE)  tag = MKTAG('m','p','4','s');
+
+    return tag;
+}
+
+static const AVCodecTag codec_ipod_tags[] = {
+    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
+    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
+    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
+    { AV_CODEC_ID_ALAC,     MKTAG('a','l','a','c') },
+    { AV_CODEC_ID_AC3,      MKTAG('a','c','-','3') },
+    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
+    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') },
+    { AV_CODEC_ID_NONE, 0 },
+};
+
+static int ipod_get_codec_tag(AVFormatContext *s, MOVTrack *track)
+{
+    int tag = track->par->codec_tag;
+
+    // keep original tag for subs, ipod supports both formats
+    if (!(track->par->codec_type == AVMEDIA_TYPE_SUBTITLE &&
+          (tag == MKTAG('t', 'x', '3', 'g') ||
+           tag == MKTAG('t', 'e', 'x', 't'))))
+        tag = ff_codec_get_tag(codec_ipod_tags, track->par->codec_id);
+
+    if (!av_match_ext(s->filename, "m4a") &&
+        !av_match_ext(s->filename, "m4b") &&
+        !av_match_ext(s->filename, "m4v"))
+        av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a, .m4v nor  .m4b "
+               "Quicktime/Ipod might not play the file\n");
+
+    return tag;
+}
+
 static int mov_get_dv_codec_tag(AVFormatContext *s, MOVTrack *track)
 {
     int tag;
@@ -1506,25 +1555,42 @@  static int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track)
     return tag;
 }
 
+static const AVCodecTag codec_3gp_tags[] = {
+    { AV_CODEC_ID_H263,     MKTAG('s','2','6','3') },
+    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
+    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
+    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
+    { AV_CODEC_ID_AMR_NB,   MKTAG('s','a','m','r') },
+    { AV_CODEC_ID_AMR_WB,   MKTAG('s','a','w','b') },
+    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
+    { AV_CODEC_ID_NONE, 0 },
+};
+
+static const AVCodecTag codec_f4v_tags[] = { // XXX: add GIF/PNG/JPEG?
+    { AV_CODEC_ID_MP3,    MKTAG('.','m','p','3') },
+    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
+    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
+    { AV_CODEC_ID_VP6A,   MKTAG('V','P','6','A') },
+    { AV_CODEC_ID_VP6F,   MKTAG('V','P','6','F') },
+    { AV_CODEC_ID_NONE, 0 },
+};
+
 static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track)
 {
     int tag;
 
     if (track->mode == MODE_MP4 || track->mode == MODE_PSP)
-        tag = track->par->codec_tag;
-    else if (track->mode == MODE_ISM)
-        tag = track->par->codec_tag;
-    else if (track->mode == MODE_IPOD) {
-        if (!av_match_ext(s->filename, "m4a") &&
-            !av_match_ext(s->filename, "m4v") &&
-            !av_match_ext(s->filename, "m4b"))
-            av_log(s, AV_LOG_WARNING, "Warning, extension is not .m4a nor .m4v "
-                   "Quicktime/Ipod might not play the file\n");
-        tag = track->par->codec_tag;
-    } else if (track->mode & MODE_3GP)
-        tag = track->par->codec_tag;
+        tag = mp4_get_codec_tag(s, track);
+    else if (track->mode == MODE_ISM) {
+        tag = mp4_get_codec_tag(s, track);
+        if (!tag && track->par->codec_id == AV_CODEC_ID_WMAPRO)
+            tag = MKTAG('w', 'm', 'a', ' ');
+    } else if (track->mode == MODE_IPOD)
+        tag = ipod_get_codec_tag(s, track);
+    else if (track->mode & MODE_3GP)
+        tag = ff_codec_get_tag(codec_3gp_tags, track->par->codec_id);
     else if (track->mode == MODE_F4V)
-        tag = track->par->codec_tag;
+        tag = ff_codec_get_tag(codec_f4v_tags, track->par->codec_id);
     else
         tag = mov_get_codec_tag(s, track);
 
@@ -2060,6 +2126,19 @@  static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track)
     return update_size(pb, pos);
 }
 
+static int mov_write_camm_tag(AVIOContext *pb) {
+    int64_t size_update;
+    int64_t pos = avio_tell(pb);
+    avio_wb32(pb, 0); /* size */
+    ffio_wfourcc(pb, "camm");
+    avio_wb32(pb, 0); /* Reserved */
+    avio_wb16(pb, 0); /* Reserved */
+    avio_wb16(pb, 1); /* Data-reference index */
+
+    size_update = update_size(pb, pos);
+    return size_update;
+}
+
 static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
 {
     int64_t pos = avio_tell(pb);
@@ -2077,6 +2156,10 @@  static int mov_write_stsd_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext
         mov_write_rtp_tag(pb, track);
     else if (track->par->codec_tag == MKTAG('t','m','c','d'))
         mov_write_tmcd_tag(pb, track);
+    else if (track->par->codec_tag
+             == ff_codec_get_tag(ff_mp4_obj_type,
+                                 AV_CODEC_ID_CAMERA_MOTION_METADATA))
+        mov_write_camm_tag(pb);
     return update_size(pb, pos);
 }
 
@@ -2443,6 +2526,11 @@  static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra
         } else if (track->par->codec_tag == MKTAG('t','m','c','d')) {
             hdlr_type = "tmcd";
             descr = "TimeCodeHandler";
+        } else if (track->par->codec_tag
+                       == ff_codec_get_tag(ff_mp4_obj_type,
+                            AV_CODEC_ID_CAMERA_MOTION_METADATA)) {
+            hdlr_type = "camm";
+            descr = "CameraMetadataMotionHandler";
         } else {
             av_log(s, AV_LOG_WARNING,
                    "Unknown hldr_type for %s, writing dummy values\n",
@@ -6422,73 +6510,6 @@  static int mov_check_bitstream(struct AVFormatContext *s, const AVPacket *pkt)
     return ret;
 }
 
-static const AVCodecTag codec_3gp_tags[] = {
-    { AV_CODEC_ID_H263,     MKTAG('s','2','6','3') },
-    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
-    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
-    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
-    { AV_CODEC_ID_AMR_NB,   MKTAG('s','a','m','r') },
-    { AV_CODEC_ID_AMR_WB,   MKTAG('s','a','w','b') },
-    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
-    { AV_CODEC_ID_NONE, 0 },
-};
-
-const AVCodecTag codec_mp4_tags[] = {
-    { AV_CODEC_ID_MPEG4       , MKTAG('m', 'p', '4', 'v') },
-    { AV_CODEC_ID_H264        , MKTAG('a', 'v', 'c', '1') },
-    { AV_CODEC_ID_HEVC        , MKTAG('h', 'e', 'v', '1') },
-    { AV_CODEC_ID_HEVC        , MKTAG('h', 'v', 'c', '1') },
-    { AV_CODEC_ID_MPEG2VIDEO  , MKTAG('m', 'p', '4', 'v') },
-    { AV_CODEC_ID_MPEG1VIDEO  , MKTAG('m', 'p', '4', 'v') },
-    { AV_CODEC_ID_MJPEG       , MKTAG('m', 'p', '4', 'v') },
-    { AV_CODEC_ID_PNG         , MKTAG('m', 'p', '4', 'v') },
-    { AV_CODEC_ID_JPEG2000    , MKTAG('m', 'p', '4', 'v') },
-    { AV_CODEC_ID_VC1         , MKTAG('v', 'c', '-', '1') },
-    { AV_CODEC_ID_DIRAC       , MKTAG('d', 'r', 'a', 'c') },
-    { AV_CODEC_ID_TSCC2       , MKTAG('m', 'p', '4', 'v') },
-    { AV_CODEC_ID_VP9         , MKTAG('v', 'p', '0', '9') },
-    { AV_CODEC_ID_AAC         , MKTAG('m', 'p', '4', 'a') },
-    { AV_CODEC_ID_MP4ALS      , MKTAG('m', 'p', '4', 'a') },
-    { AV_CODEC_ID_MP3         , MKTAG('m', 'p', '4', 'a') },
-    { AV_CODEC_ID_MP2         , MKTAG('m', 'p', '4', 'a') },
-    { AV_CODEC_ID_AC3         , MKTAG('a', 'c', '-', '3') },
-    { AV_CODEC_ID_EAC3        , MKTAG('e', 'c', '-', '3') },
-    { AV_CODEC_ID_DTS         , MKTAG('m', 'p', '4', 'a') },
-    { AV_CODEC_ID_FLAC        , MKTAG('f', 'L', 'a', 'C') },
-    { AV_CODEC_ID_OPUS        , MKTAG('O', 'p', 'u', 's') },
-    { AV_CODEC_ID_VORBIS      , MKTAG('m', 'p', '4', 'a') },
-    { AV_CODEC_ID_QCELP       , MKTAG('m', 'p', '4', 'a') },
-    { AV_CODEC_ID_EVRC        , MKTAG('m', 'p', '4', 'a') },
-    { AV_CODEC_ID_DVD_SUBTITLE, MKTAG('m', 'p', '4', 's') },
-    { AV_CODEC_ID_MOV_TEXT    , MKTAG('t', 'x', '3', 'g') },
-    { AV_CODEC_ID_NONE        ,    0 },
-};
-
-const AVCodecTag codec_ism_tags[] = {
-    { AV_CODEC_ID_WMAPRO      , MKTAG('w', 'm', 'a', ' ') },
-    { AV_CODEC_ID_NONE        ,    0 },
-};
-
-static const AVCodecTag codec_ipod_tags[] = {
-    { AV_CODEC_ID_H264,     MKTAG('a','v','c','1') },
-    { AV_CODEC_ID_MPEG4,    MKTAG('m','p','4','v') },
-    { AV_CODEC_ID_AAC,      MKTAG('m','p','4','a') },
-    { AV_CODEC_ID_ALAC,     MKTAG('a','l','a','c') },
-    { AV_CODEC_ID_AC3,      MKTAG('a','c','-','3') },
-    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','x','3','g') },
-    { AV_CODEC_ID_MOV_TEXT, MKTAG('t','e','x','t') },
-    { AV_CODEC_ID_NONE, 0 },
-};
-
-static const AVCodecTag codec_f4v_tags[] = {
-    { AV_CODEC_ID_MP3,    MKTAG('.','m','p','3') },
-    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
-    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
-    { AV_CODEC_ID_VP6A,   MKTAG('V','P','6','A') },
-    { AV_CODEC_ID_VP6F,   MKTAG('V','P','6','F') },
-    { AV_CODEC_ID_NONE, 0 },
-};
-
 #if CONFIG_MOV_MUXER
 MOV_CLASS(mov)
 AVOutputFormat ff_mov_muxer = {
@@ -6549,7 +6570,7 @@  AVOutputFormat ff_mp4_muxer = {
     .write_trailer     = mov_write_trailer,
     .deinit            = mov_free,
     .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
-    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags, 0 },
+    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
     .check_bitstream   = mov_check_bitstream,
     .priv_class        = &mp4_muxer_class,
 };
@@ -6570,7 +6591,7 @@  AVOutputFormat ff_psp_muxer = {
     .write_trailer     = mov_write_trailer,
     .deinit            = mov_free,
     .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
-    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags, 0 },
+    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
     .check_bitstream   = mov_check_bitstream,
     .priv_class        = &psp_muxer_class,
 };
@@ -6632,8 +6653,7 @@  AVOutputFormat ff_ismv_muxer = {
     .write_trailer     = mov_write_trailer,
     .deinit            = mov_free,
     .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
-    .codec_tag         = (const AVCodecTag* const []){
-        codec_mp4_tags, codec_ism_tags, 0 },
+    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
     .check_bitstream   = mov_check_bitstream,
     .priv_class        = &ismv_muxer_class,
 };