avformat/movenc: Add support for writing 'gama' atom to QuickTime .mov files.
[ffmpeg.git] / libavformat / movenc.c
index e7f4eb1..85b2ea9 100644 (file)
 #include "libavutil/avstring.h"
 #include "libavutil/intfloat.h"
 #include "libavutil/mathematics.h"
+#include "libavutil/libm.h"
 #include "libavutil/opt.h"
 #include "libavutil/dict.h"
 #include "libavutil/pixdesc.h"
 #include "libavutil/timecode.h"
+#include "libavutil/color_utils.h"
 #include "hevc.h"
 #include "rtpenc.h"
 #include "mov_chan.h"
 
-#undef NDEBUG
-#include <assert.h>
-
 static const AVOption options[] = {
     { "movflags", "MOV muxer flags", offsetof(MOVMuxContext, flags), AV_OPT_TYPE_FLAGS, {.i64 = 0}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     { "rtphint", "Add RTP hint tracks", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_RTP_HINT}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
@@ -67,6 +66,8 @@ static const AVOption options[] = {
     { "dash", "Write DASH compatible fragmented MP4", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DASH}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     { "frag_discont", "Signal that the next fragment is discontinuous from earlier ones", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_DISCONT}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     { "delay_moov", "Delay writing the initial moov until the first fragment is cut, or until the first fragment flush", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DELAY_MOOV}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+    { "write_colr", "Write colr atom (Experimental, may be renamed or changed, do not use from scripts)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_COLR}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+    { "write_gama", "Write deprecated gama atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_GAMA}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags),
     { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM},
     { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM},
@@ -79,6 +80,7 @@ static const AVOption options[] = {
     { "brand",    "Override major brand", offsetof(MOVMuxContext, major_brand),   AV_OPT_TYPE_STRING, {.str = NULL}, .flags = AV_OPT_FLAG_ENCODING_PARAM },
     { "use_editlist", "use edit list", offsetof(MOVMuxContext, use_editlist), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, AV_OPT_FLAG_ENCODING_PARAM},
     { "fragment_index", "Fragment number of the next fragment", offsetof(MOVMuxContext, fragments), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM},
+    { "mov_gamma", "gamma value for gama atom", offsetof(MOVMuxContext, gamma), AV_OPT_TYPE_FLOAT, {.dbl = 0.0 }, 0.0, 10, AV_OPT_FLAG_ENCODING_PARAM},
     { NULL },
 };
 
@@ -324,6 +326,7 @@ struct eac3_info {
     } substream[1]; /* TODO: support 8 independent substreams */
 };
 
+#if CONFIG_AC3_PARSER
 static int handle_eac3(MOVMuxContext *mov, AVPacket *pkt, MOVTrack *track)
 {
     GetBitContext gbc;
@@ -444,6 +447,7 @@ concatenate:
 
     return pkt->size;
 }
+#endif
 
 static int mov_write_eac3_tag(AVIOContext *pb, MOVTrack *track)
 {
@@ -1033,11 +1037,33 @@ static int mov_write_hvcc_tag(AVIOContext *pb, MOVTrack *track)
 static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
 {
     int i;
+    int interlaced;
+    int cid;
+
+    if (track->vos_data && track->vos_len > 0x29) {
+        if (track->vos_data[0] == 0x00 &&
+            track->vos_data[1] == 0x00 &&
+            track->vos_data[2] == 0x02 &&
+            track->vos_data[3] == 0x80 &&
+            (track->vos_data[4] == 0x01 || track->vos_data[4] == 0x02)) {
+            /* looks like a DNxHD bit stream */
+            interlaced = (track->vos_data[5] & 2);
+            cid = AV_RB32(track->vos_data + 0x28);
+        } else {
+            av_log(NULL, AV_LOG_WARNING, "Could not locate DNxHD bit stream in vos_data\n");
+            return 0;
+        }
+    } else {
+        av_log(NULL, AV_LOG_WARNING, "Could not locate DNxHD bit stream, vos_data too small\n");
+        return 0;
+    }
+
     avio_wb32(pb, 24); /* size */
     ffio_wfourcc(pb, "ACLR");
     ffio_wfourcc(pb, "ACLR");
     ffio_wfourcc(pb, "0001");
-    if (track->enc->color_range == AVCOL_RANGE_MPEG) { /* Legal range (16-235) */
+    if (track->enc->color_range == AVCOL_RANGE_MPEG || /* Legal range (16-235) */
+        track->enc->color_range == AVCOL_RANGE_UNSPECIFIED) {
         avio_wb32(pb, 1); /* Corresponds to 709 in official encoder */
     } else { /* Full range (0-255) */
         avio_wb32(pb, 2); /* Corresponds to RGB in official encoder */
@@ -1055,10 +1081,10 @@ static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
     ffio_wfourcc(pb, "ARES");
     ffio_wfourcc(pb, "ARES");
     ffio_wfourcc(pb, "0001");
-    avio_wb32(pb, AV_RB32(track->vos_data + 0x28)); /* dnxhd cid, some id ? */
+    avio_wb32(pb, cid); /* dnxhd cid, some id ? */
     avio_wb32(pb, track->enc->width);
     /* values below are based on samples created with quicktime and avid codecs */
-    if (track->vos_data[5] & 2) { // interlaced
+    if (interlaced) {
         avio_wb32(pb, track->enc->height / 2);
         avio_wb32(pb, 2); /* unknown */
         avio_wb32(pb, 0); /* unknown */
@@ -1076,8 +1102,6 @@ static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
     for (i = 0; i < 10; i++)
         avio_wb64(pb, 0);
 
-    /* extra padding for stsd needed */
-    avio_wb32(pb, 0);
     return 0;
 }
 
@@ -1499,6 +1523,104 @@ static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track)
     return 16;
 }
 
+static int mov_write_gama_tag(AVIOContext *pb, MOVTrack *track, double gamma)
+{
+    uint32_t gama = 0;
+    if (gamma <= 0.0)
+    {
+        gamma = avpriv_get_gamma_from_trc(track->enc->color_trc);
+    }
+    av_log(pb, AV_LOG_DEBUG, "gamma value %g\n", gamma);
+
+    if (gamma > 1e-6) {
+        gama = (uint32_t)lrint((double)(1<<16) * gamma);
+        av_log(pb, AV_LOG_DEBUG, "writing gama value %d\n", gama);
+
+        av_assert0(track->mode == MODE_MOV);
+        avio_wb32(pb, 12);
+        ffio_wfourcc(pb, "gama");
+        avio_wb32(pb, gama);
+        return 12;
+    }
+    else {
+        av_log(pb, AV_LOG_WARNING, "gamma value unknown, unable to write gama atom\n");
+    }
+    return 0;
+}
+
+static int mov_write_colr_tag(AVIOContext *pb, MOVTrack *track)
+{
+    // Ref (MOV): https://developer.apple.com/library/mac/technotes/tn2162/_index.html#//apple_ref/doc/uid/DTS40013070-CH1-TNTAG9
+    // Ref (MP4): ISO/IEC 14496-12:2012
+
+    if (track->enc->color_primaries == AVCOL_PRI_UNSPECIFIED &&
+        track->enc->color_trc == AVCOL_TRC_UNSPECIFIED &&
+        track->enc->colorspace == AVCOL_SPC_UNSPECIFIED) {
+        if ((track->enc->width >= 1920 && track->enc->height >= 1080)
+          || (track->enc->width == 1280 && track->enc->height == 720)) {
+            av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt709\n");
+            track->enc->color_primaries = AVCOL_PRI_BT709;
+        } else if (track->enc->width == 720 && track->height == 576) {
+            av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming bt470bg\n");
+            track->enc->color_primaries = AVCOL_PRI_BT470BG;
+        } else if (track->enc->width == 720 &&
+                   (track->height == 486 || track->height == 480)) {
+            av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, assuming smpte170\n");
+            track->enc->color_primaries = AVCOL_PRI_SMPTE170M;
+        } else {
+            av_log(NULL, AV_LOG_WARNING, "color primaries unspecified, unable to assume anything\n");
+        }
+        switch (track->enc->color_primaries) {
+        case AVCOL_PRI_BT709:
+            track->enc->color_trc = AVCOL_TRC_BT709;
+            track->enc->colorspace = AVCOL_SPC_BT709;
+            break;
+        case AVCOL_PRI_SMPTE170M:
+        case AVCOL_PRI_BT470BG:
+            track->enc->color_trc = AVCOL_TRC_BT709;
+            track->enc->colorspace = AVCOL_SPC_SMPTE170M;
+            break;
+        }
+    }
+
+    /* We should only ever be called by MOV or MP4. */
+    av_assert0(track->mode == MODE_MOV || track->mode == MODE_MP4);
+
+    avio_wb32(pb, 18 + (track->mode == MODE_MP4));
+    ffio_wfourcc(pb, "colr");
+    if (track->mode == MODE_MP4)
+        ffio_wfourcc(pb, "nclx");
+    else
+        ffio_wfourcc(pb, "nclc");
+    switch (track->enc->color_primaries) {
+    case AVCOL_PRI_BT709:     avio_wb16(pb, 1); break;
+    case AVCOL_PRI_SMPTE170M:
+    case AVCOL_PRI_SMPTE240M: avio_wb16(pb, 6); break;
+    case AVCOL_PRI_BT470BG:   avio_wb16(pb, 5); break;
+    default:                  avio_wb16(pb, 2);
+    }
+    switch (track->enc->color_trc) {
+    case AVCOL_TRC_BT709:     avio_wb16(pb, 1); break;
+    case AVCOL_TRC_SMPTE170M: avio_wb16(pb, 1); break; // remapped
+    case AVCOL_TRC_SMPTE240M: avio_wb16(pb, 7); break;
+    default:                  avio_wb16(pb, 2);
+    }
+    switch (track->enc->colorspace) {
+    case AVCOL_TRC_BT709:     avio_wb16(pb, 1); break;
+    case AVCOL_PRI_SMPTE170M: avio_wb16(pb, 6); break;
+    case AVCOL_PRI_SMPTE240M: avio_wb16(pb, 7); break;
+    default:                  avio_wb16(pb, 2);
+    }
+
+    if (track->mode == MODE_MP4) {
+        int full_range = track->enc->color_range == AVCOL_RANGE_JPEG;
+        avio_w8(pb, full_range << 7);
+        return 19;
+    } else {
+        return 18;
+    }
+}
+
 static void find_compressor(char * compressor_name, int len, MOVTrack *track)
 {
     AVDictionaryEntry *encoder;
@@ -1527,10 +1649,11 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track)
     }
 }
 
-static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track)
+static int mov_write_video_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
 {
     int64_t pos = avio_tell(pb);
     char compressor_name[32] = { 0 };
+    int avid = 0;
 
     avio_wb32(pb, 0); /* size */
     avio_wl32(pb, track->tag); // store it byteswapped
@@ -1579,9 +1702,10 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track)
             track->enc->codec_id == AV_CODEC_ID_SVQ3) {
         mov_write_extradata_tag(pb, track);
         avio_wb32(pb, 0);
-    } else if (track->enc->codec_id == AV_CODEC_ID_DNXHD)
+    } else if (track->enc->codec_id == AV_CODEC_ID_DNXHD) {
         mov_write_avid_tag(pb, track);
-    else if (track->enc->codec_id == AV_CODEC_ID_HEVC)
+        avid = 1;
+    } else if (track->enc->codec_id == AV_CODEC_ID_HEVC)
         mov_write_hvcc_tag(pb, track);
     else if (track->enc->codec_id == AV_CODEC_ID_H264 && !TAG_IS_AVCI(track->tag)) {
         mov_write_avcc_tag(pb, track);
@@ -1605,11 +1729,29 @@ static int mov_write_video_tag(AVIOContext *pb, MOVTrack *track)
         if (track->enc->field_order != AV_FIELD_UNKNOWN)
             mov_write_fiel_tag(pb, track);
 
+    if (mov->flags & FF_MOV_FLAG_WRITE_GAMA) {
+        if (track->mode == MODE_MOV)
+            mov_write_gama_tag(pb, track, mov->gamma);
+        else
+            av_log(mov->fc, AV_LOG_WARNING, "Not writing 'gama' atom. Format is not MOV.\n");
+    }
+    if (mov->flags & FF_MOV_FLAG_WRITE_COLR) {
+        if (track->mode == MODE_MOV || track->mode == MODE_MP4)
+            mov_write_colr_tag(pb, track);
+        else
+            av_log(mov->fc, AV_LOG_WARNING, "Not writing 'colr' atom. Format is not MOV or MP4.\n");
+    }
+
     if (track->enc->sample_aspect_ratio.den && track->enc->sample_aspect_ratio.num &&
         track->enc->sample_aspect_ratio.den != track->enc->sample_aspect_ratio.num) {
         mov_write_pasp_tag(pb, track);
     }
 
+    /* extra padding for avid stsd */
+    /* https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-61112 */
+    if (avid)
+        avio_wb32(pb, 0);
+
     return update_size(pb, pos);
 }
 
@@ -1695,7 +1837,7 @@ static int mov_write_tmcd_tag(AVIOContext *pb, MOVTrack *track)
     return update_size(pb, pos);
 }
 
-static int mov_write_stsd_tag(AVIOContext *pb, MOVTrack *track)
+static int mov_write_stsd_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
 {
     int64_t pos = avio_tell(pb);
     avio_wb32(pb, 0); /* size */
@@ -1703,7 +1845,7 @@ static int mov_write_stsd_tag(AVIOContext *pb, MOVTrack *track)
     avio_wb32(pb, 0); /* version & flags */
     avio_wb32(pb, 1); /* entry count */
     if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO)
-        mov_write_video_tag(pb, track);
+        mov_write_video_tag(pb, mov,  track);
     else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO)
         mov_write_audio_tag(pb, track);
     else if (track->enc->codec_type == AVMEDIA_TYPE_SUBTITLE)
@@ -1760,6 +1902,8 @@ static int mov_write_stts_tag(AVIOContext *pb, MOVTrack *track)
 
     if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO && !track->audio_vbr) {
         stts_entries = av_malloc(sizeof(*stts_entries)); /* one entry */
+        if (!stts_entries)
+            return AVERROR(ENOMEM);
         stts_entries[0].count = track->sample_count;
         stts_entries[0].duration = 1;
         entries = 1;
@@ -1767,6 +1911,8 @@ static int mov_write_stts_tag(AVIOContext *pb, MOVTrack *track)
         stts_entries = track->entry ?
                        av_malloc_array(track->entry, sizeof(*stts_entries)) : /* worst case */
                        NULL;
+        if (track->entry && !stts_entries)
+            return AVERROR(ENOMEM);
         for (i = 0; i < track->entry; i++) {
             int duration = get_cluster_duration(track, i);
             if (i && duration == stts_entries[entries].duration) {
@@ -1807,14 +1953,14 @@ static int mov_write_dref_tag(AVIOContext *pb)
     return 28;
 }
 
-static int mov_write_stbl_tag(AVIOContext *pb, MOVTrack *track)
+static int mov_write_stbl_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
 {
     int64_t pos = avio_tell(pb);
     int ret;
 
     avio_wb32(pb, 0); /* size */
     ffio_wfourcc(pb, "stbl");
-    mov_write_stsd_tag(pb, track);
+    mov_write_stsd_tag(pb, mov, track);
     mov_write_stts_tag(pb, track);
     if ((track->enc->codec_type == AVMEDIA_TYPE_VIDEO ||
          track->enc->codec_tag == MKTAG('r','t','p',' ')) &&
@@ -2032,7 +2178,7 @@ static int mov_write_hmhd_tag(AVIOContext *pb)
     return 28;
 }
 
-static int mov_write_minf_tag(AVIOContext *pb, MOVTrack *track)
+static int mov_write_minf_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
 {
     int64_t pos = avio_tell(pb);
     int ret;
@@ -2057,7 +2203,7 @@ static int mov_write_minf_tag(AVIOContext *pb, MOVTrack *track)
     if (track->mode == MODE_MOV) /* FIXME: Why do it for MODE_MOV only ? */
         mov_write_hdlr_tag(pb, NULL);
     mov_write_dinf_tag(pb);
-    if ((ret = mov_write_stbl_tag(pb, track)) < 0)
+    if ((ret = mov_write_stbl_tag(pb, mov, track)) < 0)
         return ret;
     return update_size(pb, pos);
 }
@@ -2111,7 +2257,7 @@ static int mov_write_mdia_tag(AVIOContext *pb, MOVMuxContext *mov,
     ffio_wfourcc(pb, "mdia");
     mov_write_mdhd_tag(pb, mov, track);
     mov_write_hdlr_tag(pb, track);
-    if ((ret = mov_write_minf_tag(pb, track)) < 0)
+    if ((ret = mov_write_minf_tag(pb, mov, track)) < 0)
         return ret;
     return update_size(pb, pos);
 }
@@ -4092,9 +4238,15 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
         samples_in_chunk = 1;
 
     /* copy extradata if it exists */
-    if (trk->vos_len == 0 && enc->extradata_size > 0 && !TAG_IS_AVCI(trk->tag)) {
+    if (trk->vos_len == 0 && enc->extradata_size > 0 &&
+        !TAG_IS_AVCI(trk->tag) &&
+        (enc->codec_id != AV_CODEC_ID_DNXHD)) {
         trk->vos_len  = enc->extradata_size;
         trk->vos_data = av_malloc(trk->vos_len);
+        if (!trk->vos_data) {
+            ret = AVERROR(ENOMEM);
+            goto err;
+        }
         memcpy(trk->vos_data, enc->extradata, trk->vos_len);
     }
 
@@ -4127,13 +4279,15 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
         } else {
             size = ff_hevc_annexb2mp4(pb, pkt->data, pkt->size, 0, NULL);
         }
-    } else if (CONFIG_AC3_PARSER && enc->codec_id == AV_CODEC_ID_EAC3) {
+#if CONFIG_AC3_PARSER
+    } else if (enc->codec_id == AV_CODEC_ID_EAC3) {
         size = handle_eac3(mov, pkt, trk);
         if (size < 0)
             return size;
         else if (!size)
             goto end;
         avio_write(pb, pkt->data, size);
+#endif
     } else {
         avio_write(pb, pkt->data, size);
     }
@@ -4484,12 +4638,16 @@ static int mov_create_timecode_track(AVFormatContext *s, int index, int src_inde
 
     /* encode context: tmcd data stream */
     track->enc = avcodec_alloc_context3(NULL);
+    if (!track->enc)
+        return AVERROR(ENOMEM);
     track->enc->codec_type = AVMEDIA_TYPE_DATA;
     track->enc->codec_tag  = track->tag;
     track->enc->time_base  = av_inv_q(rate);
 
     /* the tmcd track just contains one packet with the frame number */
     pkt.data = av_malloc(pkt.size);
+    if (!pkt.data)
+        return AVERROR(ENOMEM);
     AV_WB32(pkt.data, tc.start);
     ret = ff_mov_write_packet(s, &pkt);
     av_free(pkt.data);
@@ -4869,9 +5027,13 @@ static int mov_write_header(AVFormatContext *s)
         if (st->codec->extradata_size) {
             if (st->codec->codec_id == AV_CODEC_ID_DVD_SUBTITLE)
                 mov_create_dvd_sub_decoder_specific_info(track, st);
-            else if (!TAG_IS_AVCI(track->tag)){
+            else if (!TAG_IS_AVCI(track->tag) && st->codec->codec_id != AV_CODEC_ID_DNXHD) {
                 track->vos_len  = st->codec->extradata_size;
                 track->vos_data = av_malloc(track->vos_len);
+                if (!track->vos_data) {
+                    ret = AVERROR(ENOMEM);
+                    goto error;
+                }
                 memcpy(track->vos_data, st->codec->extradata, track->vos_len);
             }
         }