@@ -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"
@@ -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);
@@ -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
@@ -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;
@@ -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, \