[10/11] movenc: Add an F4F muxer

Message ID 1381320210-79941-10-git-send-email-martin@martin.st
State New
Headers show

Commit Message

Martin Storsjö Oct. 9, 2013, 12:03 p.m.
When writing F4F files, we basically fill the mdat sections of
the file with FLV packets, with the normal mp4 structures pointing
to the payload data within the FLV packets.

We are intentionally not using a chained FLV muxer, since we need
pretty accurate knowledge of what goes where, and using a chained
muxer would end up with even more code for parsing out the
necessary pieces.

This is mainly relevant as input files for serving by Adobe servers.

For serving, these files additionally need a few extra manifest
files that can be produced using a separate tool.
---
I'm not sure if anybody actually is interested in this, but I've
implemented it for completeness sake.
---
 configure                |    1 +
 libavformat/allformats.c |    1 +
 libavformat/movenc.c     |  229 ++++++++++++++++++++++++++++++++++++++++++++--
 libavformat/movenc.h     |    2 +
 libavformat/version.h    |    2 +-
 5 files changed, 228 insertions(+), 7 deletions(-)

Patch

diff --git a/configure b/configure
index b018f05..4e018f9 100755
--- a/configure
+++ b/configure
@@ -1814,6 +1814,7 @@  caf_demuxer_select="riffdec"
 dirac_demuxer_select="dirac_parser"
 dxa_demuxer_select="riffdec"
 eac3_demuxer_select="ac3_parser"
+f4f_muxer_select="mov_muxer"
 f4v_muxer_select="mov_muxer"
 flac_demuxer_select="flac_parser"
 hds_muxer_select="flv_muxer"
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 236a511..322e19f 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -100,6 +100,7 @@  void av_register_all(void)
     REGISTER_DEMUXER (EA,               ea);
     REGISTER_DEMUXER (EA_CDATA,         ea_cdata);
     REGISTER_MUXDEMUX(EAC3,             eac3);
+    REGISTER_MUXER   (F4F,              f4f);
     REGISTER_MUXER   (F4V,              f4v);
     REGISTER_MUXDEMUX(FFM,              ffm);
     REGISTER_MUXDEMUX(FFMETADATA,       ffmetadata);
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 38ac13f..ee6b5c1 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -39,6 +39,7 @@ 
 #include "libavutil/dict.h"
 #include "rtpenc.h"
 #include "mov_chan.h"
+#include "flv.h"
 
 #undef NDEBUG
 #include <assert.h>
@@ -897,6 +898,12 @@  static const AVCodecTag codec_f4v_tags[] = {
     { AV_CODEC_ID_NONE, 0 },
 };
 
+static const AVCodecTag codec_f4f_tags[] = {
+    { AV_CODEC_ID_AAC,    MKTAG('m','p','4','a') },
+    { AV_CODEC_ID_H264,   MKTAG('a','v','c','1') },
+    { AV_CODEC_ID_NONE, 0 },
+};
+
 static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track)
 {
     int tag;
@@ -913,6 +920,8 @@  static int mov_find_codec_tag(AVFormatContext *s, MOVTrack *track)
         tag = ff_codec_get_tag(codec_3gp_tags, track->enc->codec_id);
     else if (track->mode == MODE_F4V)
         tag = ff_codec_get_tag(codec_f4v_tags, track->enc->codec_id);
+    else if (track->mode == MODE_F4F)
+        tag = ff_codec_get_tag(codec_f4f_tags, track->enc->codec_id);
     else
         tag = mov_get_codec_tag(s, track);
 
@@ -2569,7 +2578,7 @@  static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s)
         ffio_wfourcc(pb, has_video ? "M4V ":"M4A ");
     else if (mov->mode == MODE_ISM)
         ffio_wfourcc(pb, "isml");
-    else if (mov->mode == MODE_F4V)
+    else if (mov->mode == MODE_F4V || mov->mode == MODE_F4F)
         ffio_wfourcc(pb, "f4v ");
     else
         ffio_wfourcc(pb, "qt  ");
@@ -2738,6 +2747,127 @@  static void mov_parse_vc1_frame(AVPacket *pkt, MOVTrack *trk, int fragment)
     }
 }
 
+static int mov_write_afra_tag(AVIOContext *pb, MOVMuxContext *mov,
+                              MOVTrack *track)
+{
+    int64_t pos = avio_tell(pb);
+
+    avio_wb32(pb, 0); /* size placeholder */
+    ffio_wfourcc(pb, "afra");
+    avio_wb32(pb, 0); // version + flags
+    avio_w8(pb, 0xc0); // long ids, long offsets, no global entries
+    avio_wb32(pb, 1000); // timescale
+    avio_wb32(pb, 1); // entry count
+    avio_wb64(pb, track->frag_info[track->nb_frag_info - 1].time);
+    track->afra_sample_offset = avio_tell(pb);
+    avio_wb64(pb, 0);
+
+    return update_size(pb, pos);
+}
+
+static int mov_write_asrt_tag(AVIOContext *pb, MOVMuxContext *mov,
+                              MOVTrack *track)
+{
+    int64_t pos = avio_tell(pb);
+    avio_wb32(pb, 0);
+    ffio_wfourcc(pb, "asrt");
+    avio_wb32(pb, 0); // version + flags
+    avio_w8(pb, 0); // QualityEntryCount
+    avio_wb32(pb, 1); // SegmentRunEntryCount
+    avio_wb32(pb, 1); // FirstSegment
+    avio_wb32(pb, track->nb_frag_info); // FragmentsPerSegment
+
+    return update_size(pb, pos);
+}
+
+static int mov_write_afrt_tag(AVIOContext *pb, MOVMuxContext *mov,
+                              MOVTrack *track)
+{
+    int64_t pos = avio_tell(pb);
+    int i;
+    avio_wb32(pb, 0);
+    ffio_wfourcc(pb, "afrt");
+    avio_wb32(pb, 0); // version + flags
+    avio_wb32(pb, 1000); // timescale
+    avio_w8(pb, 0); // QualityEntryCount
+    avio_wb32(pb, track->nb_frag_info); // FragmentRunEntryCount
+    for (i = 0; i < track->nb_frag_info; i++) {
+        avio_wb32(pb, i + 1);
+        avio_wb64(pb, track->frag_info[i].time);
+        avio_wb32(pb, track->frag_info[i].duration);
+    }
+    return update_size(pb, pos);
+}
+
+static int mov_write_abst_tag(AVIOContext *pb, MOVMuxContext *mov,
+                              MOVTrack *track)
+{
+    int64_t pos = avio_tell(pb);
+
+    avio_wb32(pb, 0); /* size placeholder */
+    ffio_wfourcc(pb, "abst");
+    avio_wb32(pb, 0); // version + flags
+    avio_wb32(pb, track->nb_frag_info - 1); // BootstrapinfoVersion
+    avio_w8(pb, 0); // profile, live, update
+    avio_wb32(pb, 1000); // timescale
+    avio_wb64(pb, track->frag_info[track->nb_frag_info - 1].time +
+                  track->frag_info[track->nb_frag_info - 1].duration);
+    avio_wb64(pb, 0); // SmpteTimeCodeOffset
+    avio_w8(pb, 0); // MovieIdentifer (null string)
+    avio_w8(pb, 0); // ServerEntryCount
+    avio_w8(pb, 0); // QualityEntryCount
+    avio_w8(pb, 0); // DrmData (null string)
+    avio_w8(pb, 0); // MetaData (null string)
+    avio_w8(pb, 1); // SegmentRunTableCount
+
+    mov_write_asrt_tag(pb, mov, track);
+
+    avio_w8(pb, 1); // FragmentRunTableCount
+
+    mov_write_afrt_tag(pb, mov, track);
+
+    return update_size(pb, pos);
+}
+
+static void write_f4f_global_headers(AVIOContext *pb, AVFormatContext *s,
+                                     int64_t dts)
+{
+    MOVMuxContext *mov = s->priv_data;
+    int i;
+    for (i = 0; i < s->nb_streams; i++) {
+        AVCodecContext *enc = s->streams[i]->codec;
+        if (enc->codec_id == AV_CODEC_ID_AAC ||
+            enc->codec_id == AV_CODEC_ID_H264) {
+            int64_t pos;
+            int data_size;
+            avio_w8(pb, enc->codec_type == AVMEDIA_TYPE_VIDEO ?
+                    FLV_TAG_TYPE_VIDEO : FLV_TAG_TYPE_AUDIO);
+            avio_wb24(pb, 0); // size patched later
+            avio_wb24(pb, dts); // ts
+            avio_w8(pb, (dts >> 24) & 0x7F);
+            avio_wb24(pb, 0); // streamid
+            pos = avio_tell(pb);
+            if (enc->codec_id == AV_CODEC_ID_AAC) {
+                avio_w8(pb, FLV_CODECID_AAC | FLV_SAMPLERATE_44100HZ |
+                            FLV_SAMPLESSIZE_16BIT | FLV_STEREO);
+                avio_w8(pb, 0); // AAC sequence header
+                avio_write(pb, enc->extradata, enc->extradata_size);
+            } else {
+                avio_w8(pb, FLV_FRAME_KEY | FLV_CODECID_H264);
+                avio_w8(pb, 0); // AVC sequence header
+                avio_wb24(pb, 0); // composition time
+                ff_isom_write_avcc(pb, enc->extradata, enc->extradata_size);
+            }
+            data_size = avio_tell(pb) - pos;
+            avio_seek(pb, -data_size - 10, SEEK_CUR);
+            avio_wb24(pb, data_size);
+            avio_skip(pb, data_size + 7);
+            avio_wb32(pb, data_size + 11); // previous tag size
+            mov->mdat_size += data_size + 11 + 4;
+        }
+    }
+}
+
 static int mov_flush_fragment(AVFormatContext *s)
 {
     MOVMuxContext *mov = s->priv_data;
@@ -2833,7 +2963,9 @@  static int mov_flush_fragment(AVFormatContext *s)
 
         if (write_moof) {
             MOVFragmentInfo *info;
+            int64_t start;
             avio_flush(s->pb);
+            start = avio_tell(s->pb);
             track->nb_frag_info++;
             if (track->nb_frag_info >= track->frag_info_capacity) {
                 unsigned new_capacity = track->nb_frag_info + MOV_FRAG_INFO_ALLOC_INCREMENT;
@@ -2849,10 +2981,22 @@  static int mov_flush_fragment(AVFormatContext *s)
             info->duration = duration;
             mov_write_tfrf_tags(s->pb, mov, track);
 
+            if (mov->mode == MODE_F4F) {
+                mov_write_afra_tag(s->pb, mov, track);
+                mov_write_abst_tag(s->pb, mov, track);
+            }
+
             mov_write_moof_tag(s->pb, mov, moof_tracks);
             info->tfrf_offset = track->tfrf_offset;
             mov->fragments++;
 
+            if (mov->mode == MODE_F4F) {
+                int64_t pos = avio_tell(s->pb);
+                avio_seek(s->pb, track->afra_sample_offset, SEEK_SET);
+                avio_wb64(s->pb, pos - start + 8);
+                avio_seek(s->pb, pos, SEEK_SET);
+            }
+
             avio_wb32(s->pb, mdat_size + 8);
             ffio_wfourcc(s->pb, "mdat");
         }
@@ -2891,6 +3035,7 @@  int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
     unsigned int samples_in_chunk = 0;
     int size = pkt->size;
     uint8_t *reformatted_data = NULL;
+    int flv_header_size = 0;
 
     if (mov->flags & FF_MOV_FLAG_FRAGMENT) {
         int ret;
@@ -2935,6 +3080,41 @@  int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
         memcpy(trk->vos_data, enc->extradata, trk->vos_len);
     }
 
+    if (mov->mode == MODE_F4F) {
+        int extra = 0;
+        int64_t dts = pkt->dts;
+        if (trk->start_dts != AV_NOPTS_VALUE)
+            dts -= trk->start_dts;
+        else
+            dts = 0;
+
+        if (avio_tell(pb) == 0)
+            write_f4f_global_headers(pb, s, dts);
+
+        avio_w8(pb, enc->codec_type == AVMEDIA_TYPE_VIDEO ?
+                    FLV_TAG_TYPE_VIDEO : FLV_TAG_TYPE_AUDIO);
+        if (enc->codec_id == AV_CODEC_ID_H264)
+            extra = 5;
+        else if (enc->codec_id == AV_CODEC_ID_AAC)
+            extra = 2;
+        avio_wb24(pb, size + extra);
+        avio_wb24(pb, dts);
+        avio_w8(pb, (dts >> 24) & 0x7F);
+        avio_wb24(pb, 0);
+
+        if (enc->codec_id == AV_CODEC_ID_H264) {
+            avio_w8(pb, (pkt->flags & AV_PKT_FLAG_KEY ?
+                         FLV_FRAME_KEY : FLV_FRAME_INTER) | FLV_CODECID_H264);
+            avio_w8(pb, 1); // AVC NALU
+            avio_wb24(pb, pkt->pts - pkt->dts);
+        } else if (enc->codec_id == AV_CODEC_ID_AAC) {
+            avio_w8(pb, FLV_CODECID_AAC | FLV_SAMPLERATE_44100HZ |
+                        FLV_SAMPLESSIZE_16BIT | FLV_STEREO);
+            avio_w8(pb, 1); // AAC raw
+        }
+        flv_header_size = extra + 11;
+    }
+
     if (enc->codec_id == AV_CODEC_ID_H264 && trk->vos_len > 0 && *(uint8_t *)trk->vos_data != 1) {
         /* from x264 or from bytestream h264 */
         /* nal reformating needed */
@@ -2945,6 +3125,12 @@  int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
         } else {
             size = ff_avc_parse_nal_units(pb, pkt->data, pkt->size);
         }
+        if (mov->mode == MODE_F4F) {
+            int64_t pos = avio_tell(pb);
+            avio_seek(pb, -size - 15, SEEK_CUR);
+            avio_wb24(pb, size + 5);
+            avio_seek(pb, pos, SEEK_SET);
+        }
     } else {
         avio_write(pb, pkt->data, size);
     }
@@ -3009,6 +3195,11 @@  int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
     trk->sample_count += samples_in_chunk;
     mov->mdat_size    += size;
 
+    if (mov->mode == MODE_F4F) {
+        avio_wb32(pb, size + flv_header_size);
+        mov->mdat_size += flv_header_size + 4;
+    }
+
     if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams)
         ff_mov_add_hinted_packet(s, pkt, trk->hint_track, trk->entry,
                                  reformatted_data, size);
@@ -3189,6 +3380,7 @@  static int mov_write_header(AVFormatContext *s)
         else if (!strcmp("ipod",s->oformat->name)) mov->mode = MODE_IPOD;
         else if (!strcmp("ismv",s->oformat->name)) mov->mode = MODE_ISM;
         else if (!strcmp("f4v", s->oformat->name)) mov->mode = MODE_F4V;
+        else if (!strcmp("f4f", s->oformat->name)) mov->mode = MODE_F4F;
     }
 
     /* Set the FRAGMENT flag if any of the fragmentation methods are
@@ -3200,9 +3392,13 @@  static int mov_write_header(AVFormatContext *s)
         mov->flags |= FF_MOV_FLAG_FRAGMENT;
 
     /* Set other implicit flags immediately */
-    if (mov->mode == MODE_ISM)
-        mov->flags |= FF_MOV_FLAG_EMPTY_MOOV | FF_MOV_FLAG_SEPARATE_MOOF |
-                      FF_MOV_FLAG_FRAGMENT;
+    if (mov->mode == MODE_ISM || mov->mode == MODE_F4F) {
+        mov->flags |= FF_MOV_FLAG_EMPTY_MOOV | FF_MOV_FLAG_FRAGMENT;
+        if (mov->mode == MODE_ISM)
+            mov->flags |= FF_MOV_FLAG_SEPARATE_MOOF;
+        else if (mov->mode == MODE_F4F)
+            mov->flags |= FF_MOV_FLAG_FRAG_INTERLEAVED;
+    }
 
     /* faststart: moov at the beginning of the file, if supported */
     if (mov->flags & FF_MOV_FLAG_FASTSTART) {
@@ -3330,6 +3526,8 @@  static int mov_write_header(AVFormatContext *s)
          * some tools, such as mp4split. */
         if (mov->mode == MODE_ISM)
             track->timescale = 10000000;
+        if (mov->mode == MODE_F4F)
+            track->timescale = 1000;
 
         avpriv_set_pts_info(st, 64, 1, track->timescale);
 
@@ -3343,7 +3541,7 @@  static int mov_write_header(AVFormatContext *s)
 
     enable_tracks(s);
 
-    if (mov->mode == MODE_ISM) {
+    if (mov->mode == MODE_ISM || mov->mode == MODE_F4F) {
         /* If no fragmentation options have been set, set a default. */
         if (!(mov->flags & (FF_MOV_FLAG_FRAG_KEYFRAME |
                             FF_MOV_FLAG_FRAG_CUSTOM)) &&
@@ -3549,7 +3747,8 @@  static int mov_write_trailer(AVFormatContext *s)
         }
     } else {
         mov_flush_fragment(s);
-        mov_write_mfra_tag(pb, mov);
+        if (mov->mode != MODE_F4F)
+            mov_write_mfra_tag(pb, mov);
     }
 
     for (i = 0; i < mov->nb_streams; i++) {
@@ -3716,3 +3915,21 @@  AVOutputFormat ff_f4v_muxer = {
     .priv_class        = &f4v_muxer_class,
 };
 #endif
+#if CONFIG_F4F_MUXER
+MOV_CLASS(f4f)
+AVOutputFormat ff_f4f_muxer = {
+    .name              = "f4f",
+    .long_name         = NULL_IF_CONFIG_SMALL("F4F Adobe Flash Video"),
+    .mime_type         = "application/f4v",
+    .extensions        = "f4f",
+    .priv_data_size    = sizeof(MOVMuxContext),
+    .audio_codec       = AV_CODEC_ID_AAC,
+    .video_codec       = AV_CODEC_ID_H264,
+    .write_header      = mov_write_header,
+    .write_packet      = mov_write_packet,
+    .write_trailer     = mov_write_trailer,
+    .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
+    .codec_tag         = (const AVCodecTag* const []){ codec_f4f_tags, 0 },
+    .priv_class        = &f4f_muxer_class,
+};
+#endif
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index 0be14d3..1b1cbbd 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -41,6 +41,7 @@ 
 #define MODE_IPOD 0x20
 #define MODE_ISM  0x40
 #define MODE_F4V  0x80
+#define MODE_F4F 0x100
 
 typedef struct MOVIentry {
     uint64_t     pos;
@@ -120,6 +121,7 @@  typedef struct MOVTrack {
     int64_t     data_offset;
     int64_t     frag_start;
     int64_t     tfrf_offset;
+    int64_t     afra_sample_offset;
 
     int         nb_frag_info;
     MOVFragmentInfo *frag_info;
diff --git a/libavformat/version.h b/libavformat/version.h
index 43431a5..12f7892 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -30,7 +30,7 @@ 
 #include "libavutil/avutil.h"
 
 #define LIBAVFORMAT_VERSION_MAJOR 55
-#define LIBAVFORMAT_VERSION_MINOR  7
+#define LIBAVFORMAT_VERSION_MINOR  8
 #define LIBAVFORMAT_VERSION_MICRO  0
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \