]> git.sesse.net Git - ffmpeg/blobdiff - libavformat/movenc.c
Merge remote-tracking branch 'qatar/master'
[ffmpeg] / libavformat / movenc.c
index 6ed1005d33e1520c543aeaee8da76c15d8599027..4574df3de979a5a824f7d27db3b34d8ce847c18f 100644 (file)
@@ -50,6 +50,7 @@ static const AVOption options[] = {
     { "frag_keyframe", "Fragment at video keyframes", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_FRAG_KEYFRAME}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     { "separate_moof", "Write separate moof/mdat atoms for each track", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
     { "frag_custom", "Flush fragments on caller requests", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_FRAG_CUSTOM}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+    { "isml", "Create a live smooth streaming feed (for pushing to a publishing point)", 0, AV_OPT_TYPE_CONST, {.dbl = FF_MOV_FLAG_ISML}, 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, {.dbl = 1}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM},
     { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.dbl = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM},
@@ -1975,6 +1976,128 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
     return updateSize(pb, pos);
 }
 
+static void param_write_int(AVIOContext *pb, const char *name, int value)
+{
+    avio_printf(pb, "<param name=\"%s\" value=\"%d\" valuetype=\"data\"/>\n", name, value);
+}
+
+static void param_write_string(AVIOContext *pb, const char *name, const char *value)
+{
+    avio_printf(pb, "<param name=\"%s\" value=\"%s\" valuetype=\"data\"/>\n", name, value);
+}
+
+static void param_write_hex(AVIOContext *pb, const char *name, const uint8_t *value, int len)
+{
+    char buf[150];
+    len = FFMIN(sizeof(buf)/2 - 1, len);
+    ff_data_to_hex(buf, value, len, 0);
+    buf[2*len] = '\0';
+    avio_printf(pb, "<param name=\"%s\" value=\"%s\" valuetype=\"data\"/>\n", name, buf);
+}
+
+static void write_h264_extradata(AVIOContext *pb, AVCodecContext *enc)
+{
+    uint16_t sps_size, pps_size, len;
+    char buf[150];
+    sps_size = AV_RB16(&enc->extradata[6]);
+    if (11 + sps_size > enc->extradata_size)
+        return;
+    pps_size = AV_RB16(&enc->extradata[9 + sps_size]);
+    if (11 + sps_size + pps_size > enc->extradata_size)
+        return;
+    len = FFMIN(sizeof(buf)/2 - 1, sps_size);
+    ff_data_to_hex(buf, &enc->extradata[8], len, 0);
+    buf[2*len] = '\0';
+    avio_printf(pb, "<param name=\"CodecPrivateData\" value=\"00000001%s", buf);
+    len = FFMIN(sizeof(buf)/2 - 1, pps_size);
+    ff_data_to_hex(buf, &enc->extradata[11 + sps_size], len, 0);
+    buf[2*len] = '\0';
+    avio_printf(pb, "00000001%s\" valuetype=\"data\"/>\n", buf);
+}
+
+static int mov_write_isml_manifest(AVIOContext *pb, MOVMuxContext *mov)
+{
+    int64_t pos = avio_tell(pb);
+    int i;
+    const uint8_t uuid[] = {
+        0xa5, 0xd4, 0x0b, 0x30, 0xe8, 0x14, 0x11, 0xdd,
+        0xba, 0x2f, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66
+    };
+
+    avio_wb32(pb, 0);
+    ffio_wfourcc(pb, "uuid");
+    avio_write(pb, uuid, sizeof(uuid));
+    avio_wb32(pb, 0);
+
+    avio_printf(pb, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+    avio_printf(pb, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
+    avio_printf(pb, "<head>\n");
+    avio_printf(pb, "<meta name=\"creator\" content=\"%s\" />\n",
+                    LIBAVFORMAT_IDENT);
+    avio_printf(pb, "</head>\n");
+    avio_printf(pb, "<body>\n");
+    avio_printf(pb, "<switch>\n");
+    for (i = 0; i < mov->nb_streams; i++) {
+        MOVTrack *track = &mov->tracks[i];
+        const char *type;
+        /* track->trackID is initialized in write_moov, and thus isn't known
+         * here yet */
+        int track_id = i + 1;
+
+        if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) {
+            type = "video";
+        } else if (track->enc->codec_type == AVMEDIA_TYPE_AUDIO) {
+            type = "audio";
+        } else {
+            continue;
+        }
+        avio_printf(pb, "<%s systemBitrate=\"%d\">\n", type,
+                                                       track->enc->bit_rate);
+        param_write_int(pb, "systemBitrate", track->enc->bit_rate);
+        param_write_int(pb, "trackID", track_id);
+        if (track->enc->codec_type == AVMEDIA_TYPE_VIDEO) {
+            if (track->enc->codec_id == CODEC_ID_H264 &&
+                track->enc->extradata_size >= 11 &&
+                track->enc->extradata[0] == 1) {
+                write_h264_extradata(pb, track->enc);
+            } else {
+                param_write_hex(pb, "CodecPrivateData", track->enc->extradata,
+                                track->enc->extradata_size);
+            }
+            if (track->enc->codec_id == CODEC_ID_H264) {
+                param_write_string(pb, "FourCC", "H264");
+            } else if (track->enc->codec_id == CODEC_ID_VC1) {
+                param_write_string(pb, "FourCC", "WVC1");
+            }
+            param_write_int(pb, "MaxWidth", track->enc->width);
+            param_write_int(pb, "MaxHeight", track->enc->height);
+            param_write_int(pb, "DisplayWidth", track->enc->width);
+            param_write_int(pb, "DisplayHeight", track->enc->height);
+        } else {
+            if (track->enc->codec_id == CODEC_ID_AAC) {
+                param_write_string(pb, "FourCC", "AACL");
+            } else if (track->enc->codec_id == CODEC_ID_WMAPRO) {
+                param_write_string(pb, "FourCC", "WMAP");
+            }
+            param_write_hex(pb, "CodecPrivateData", track->enc->extradata,
+                            track->enc->extradata_size);
+            param_write_int(pb, "AudioTag", ff_codec_get_tag(ff_codec_wav_tags,
+                                                track->enc->codec_id));
+            param_write_int(pb, "Channels", track->enc->channels);
+            param_write_int(pb, "SamplingRate", track->enc->sample_rate);
+            param_write_int(pb, "BitsPerSample", 16);
+            param_write_int(pb, "PacketSize", track->enc->block_align ?
+                                              track->enc->block_align : 4);
+        }
+        avio_printf(pb, "</%s>\n", type);
+    }
+    avio_printf(pb, "</switch>\n");
+    avio_printf(pb, "</body>\n");
+    avio_printf(pb, "</smil>\n");
+
+    return updateSize(pb, pos);
+}
+
 static int mov_write_mfhd_tag(AVIOContext *pb, MOVMuxContext *mov)
 {
     avio_wb32(pb, 16);
@@ -2245,6 +2368,10 @@ static int mov_write_mfra_tag(AVIOContext *pb, MOVMuxContext *mov)
 
     avio_wb32(pb, 0); /* size placeholder */
     ffio_wfourcc(pb, "mfra");
+    /* An empty mfra atom is enough to indicate to the publishing point that
+     * the stream has ended. */
+    if (mov->flags & FF_MOV_FLAG_ISML)
+        return updateSize(pb, pos);
 
     for (i = 0; i < mov->nb_streams; i++) {
         MOVTrack *track = &mov->tracks[i];
@@ -2446,6 +2573,7 @@ static int mov_flush_fragment(AVFormatContext *s)
                                              mov->tracks[i].cluster[0].dts;
             mov->tracks[i].entry = 0;
         }
+        avio_flush(s->pb);
         return 0;
     }
 
@@ -2485,6 +2613,7 @@ static int mov_flush_fragment(AVFormatContext *s)
 
         if (write_moof) {
             MOVFragmentInfo *info;
+            avio_flush(s->pb);
             track->nb_frag_info++;
             track->frag_info = av_realloc(track->frag_info,
                                           sizeof(*track->frag_info) *
@@ -2517,6 +2646,7 @@ static int mov_flush_fragment(AVFormatContext *s)
 
     mov->mdat_size = 0;
 
+    avio_flush(s->pb);
     return 0;
 }
 
@@ -2530,7 +2660,9 @@ static int mov_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
     int size= pkt->size;
     uint8_t *reformatted_data = NULL;
 
-    if (!s->pb->seekable) return 0; /* Can't handle that */
+    if (!s->pb->seekable && !(mov->flags & FF_MOV_FLAG_EMPTY_MOOV))
+        return 0; /* Can't handle that */
+
     if (!size) return 0; /* Discard 0 sized packets */
 
     if ((mov->max_fragment_duration && trk->entry &&
@@ -2718,7 +2850,13 @@ static int mov_write_header(AVFormatContext *s)
     AVDictionaryEntry *t;
     int i, hint_track = 0;
 
-    if (!s->pb->seekable) {
+    /* Non-seekable output is ok if EMPTY_MOOV is set, or if using the ismv
+     * format (which sets EMPTY_MOOV later in this function). If ism_lookahead
+     * is enabled, we don't support non-seekable output at all. */
+    if (!s->pb->seekable &&
+        ((!(mov->flags & FF_MOV_FLAG_EMPTY_MOOV) &&
+          !(s->oformat && !strcmp(s->oformat->name, "ismv")))
+         || mov->ism_lookahead)) {
         av_log(s, AV_LOG_ERROR, "muxer does not support non seekable output\n");
         return -1;
     }
@@ -2748,14 +2886,6 @@ static int mov_write_header(AVFormatContext *s)
     if (mov->mode & (MODE_MOV|MODE_IPOD) && s->nb_chapters)
         mov->chapter_track = mov->nb_streams++;
 
-#if FF_API_FLAG_RTP_HINT
-    if (s->flags & AVFMT_FLAG_RTP_HINT) {
-        av_log(s, AV_LOG_WARNING, "The RTP_HINT flag is deprecated, enable it "
-                                  "via the -movflags rtphint muxer option "
-                                  "instead.\n");
-        mov->flags |= FF_MOV_FLAG_RTP_HINT;
-    }
-#endif
     if (mov->flags & FF_MOV_FLAG_RTP_HINT) {
         /* Add hint tracks for each audio and video stream */
         hint_track = mov->nb_streams;
@@ -2883,11 +3013,6 @@ static int mov_write_header(AVFormatContext *s)
     if (!(mov->flags & FF_MOV_FLAG_EMPTY_MOOV))
         mov_write_mdat_tag(pb, mov);
 
-#if FF_API_TIMESTAMP
-    if (s->timestamp)
-        mov->time = s->timestamp;
-    else
-#endif
     if (t = av_dict_get(s->metadata, "creation_time", NULL, 0))
         mov->time = ff_iso8601_to_unix_time(t->value);
     if (mov->time)
@@ -2910,6 +3035,9 @@ static int mov_write_header(AVFormatContext *s)
 
     avio_flush(pb);
 
+    if (mov->flags & FF_MOV_FLAG_ISML)
+        mov_write_isml_manifest(pb, mov);
+
     if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV) {
         mov_write_moov_tag(pb, mov, s);
         mov->fragments++;