[FFmpeg-devel] [PATCH] lavf: F3M spec, muxer, demuxer and tools. (WIP)

Nicolas George nicolas.george at normalesup.org
Sat Aug 4 20:19:45 CEST 2012


Signed-off-by: Nicolas George <nicolas.george at normalesup.org>
---
 doc/f3m.txt              |   75 ++++++++++
 libavformat/Makefile     |    2 +
 libavformat/allformats.c |    1 +
 libavformat/f3m.c        |   54 +++++++
 libavformat/f3mdec.c     |  359 ++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/f3menc.c     |  316 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 807 insertions(+)
 create mode 100644 doc/f3m.txt
 create mode 100644 libavformat/f3m.c
 create mode 100644 libavformat/f3mdec.c
 create mode 100644 libavformat/f3menc.c


This is the format that I was writing about a few days ago: it allows to
remux almost anything and to extract a portion of the file very fast.

This is not finished: the extra tools are missing (especially the one that
can extract a part using hardlinks) and some more testing is required (at
least I would like to try MPlayer), but it already works.

The design is the same that has been privately used for two years, with
minor fixes and enhancements.

The goal of the format design is to keep things simple. I would like to know
if deep changes will be necessary before starting to port the tools.

Regards,

-- 
  Nicolas George


diff --git a/doc/f3m.txt b/doc/f3m.txt
new file mode 100644
index 0000000..dca16ba
--- /dev/null
+++ b/doc/f3m.txt
@@ -0,0 +1,75 @@
+The F3M file(s) format
+
+Introduction
+============
+
+F3M stands for FFmpeg Fragmented Multimedia. It is a format designed
+primarily for storage of recordings. The main design constraint is to allow
+accurate and efficient cutting of the video. It is achieved by splitting the
+bulk of the data into reasonably-sized files: extraction can be done mostly
+using hardlinks.
+
+
+Format specification
+====================
+
+General conventions
+-------------------
+
+All numbers are in big endian; signed numbers are in 2's complement.
+
+All timestamps are in milliseconds.
+
+The first stream must be a video stream and is called the "reference
+stream".
+
+The header and index file is called basename.f3m; the data payload files are
+called basename-number.f3md, where number is a 5-digits decimal number
+starting at 00000.
+
+The payload data is the result of the virtual concatenation of all payload
+files, in ascending order. The separation between payload files must happen
+at the beginning of a keyframe packet of the reference stream.
+
+Header and index file
+---------------------
+
+Header:
+
+  Field type      Field description
+
+  U8[8]           magic string: 'F3MHEAD\0'
+  U8              number of streams, <= 127
+  U32             size of the file metadata
+  U8[n]           file metadata, key-values pairs, terminated by \0
+  Stream[n]       stream headers
+  U32             number of index entries (one per keyframe in the ref stream)
+  U16             last fragment
+  Index[n]        index entries
+
+Streams:
+
+  U8              type, same value as enum AVMediaType
+  U64             total number of octets
+  U64             total number of packets
+  U32             expected DTS of the next packet, can count as duration
+  U32             size of the codec name and metadata
+  U8[n]           codec name and metadata as key-value pairs, terminated by \0
+  U32             size of the codec extradata
+  U8[n]           codec extradata
+
+Index:
+
+  U32             DTS
+  U16             fragment number
+  U32             offset in the fragment
+  U32[n]          number of octets in each stream until the next index entry
+  U16[n]          number of packets in each stream until the next index entry
+
+Packets:
+
+  U8              stream number, +0x80 if keyframe
+  U24             packet size
+  U16             PTS - DTS
+  U8[n]           data
+  U16             dDTS = DTS of next packet - DTS
diff --git a/libavformat/Makefile b/libavformat/Makefile
index d77c9ea..9d751dd 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -88,6 +88,8 @@ OBJS-$(CONFIG_EA_CDATA_DEMUXER)          += eacdata.o
 OBJS-$(CONFIG_EA_DEMUXER)                += electronicarts.o
 OBJS-$(CONFIG_EAC3_DEMUXER)              += ac3dec.o rawdec.o
 OBJS-$(CONFIG_EAC3_MUXER)                += rawenc.o
+OBJS-$(CONFIG_F3M_DEMUXER)               += f3mdec.o f3m.o
+OBJS-$(CONFIG_F3M_MUXER)                 += f3menc.o f3m.o
 OBJS-$(CONFIG_FFM_DEMUXER)               += ffmdec.o
 OBJS-$(CONFIG_FFM_MUXER)                 += ffmenc.o
 OBJS-$(CONFIG_FFMETADATA_DEMUXER)        += ffmetadec.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index d1b41e6..bd80416 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -93,6 +93,7 @@ void av_register_all(void)
     REGISTER_DEMUXER  (EA, ea);
     REGISTER_DEMUXER  (EA_CDATA, ea_cdata);
     REGISTER_MUXDEMUX (EAC3, eac3);
+    REGISTER_MUXDEMUX (F3M, f3m);
     REGISTER_MUXDEMUX (FFM, ffm);
     REGISTER_MUXDEMUX (FFMETADATA, ffmetadata);
     REGISTER_MUXDEMUX (FILMSTRIP, filmstrip);
diff --git a/libavformat/f3m.c b/libavformat/f3m.c
new file mode 100644
index 0000000..6a2c09b
--- /dev/null
+++ b/libavformat/f3m.c
@@ -0,0 +1,54 @@
+/*
+ * F3M file format common functions
+ * Copyright (c) 2012 Nicolas George
+ *
+ * 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
+ */
+
+#include "libavutil/opt.h"
+#include "f3m.h"
+
+int ff_f3m_check_filename(const char *filename)
+{
+    size_t len = strlen(filename);
+    AVFormatContext *avf;
+
+    return len > 4 && len <= sizeof(avf->filename) - 1 ||
+           !memcmp(filename + len - 4, ".f3m", 4);
+}
+
+int ff_f3m_open_fragment(AVFormatContext *avf,
+                         AVIOContext **rpb, int *rnum, int num, int flags)
+{
+    AVIOContext *pb = NULL;
+    char filename[sizeof(avf->filename) + 7];
+    int ret;
+    size_t len;
+
+    len = strlen(avf->filename);
+    memcpy(filename, avf->filename, len - 4);
+    snprintf(filename + len - 4, sizeof(filename) - (len - 4),
+             "-%05d.f3md", num);
+    if ((ret = avio_open(&pb, filename, flags)) < 0) {
+        av_log(avf, AV_LOG_ERROR, "%s: %s\n", filename, av_err2str(ret));
+        return ret;
+    }
+    avio_close(*rpb);
+    *rpb  = pb;
+    *rnum = num;
+    return 0;
+}
diff --git a/libavformat/f3mdec.c b/libavformat/f3mdec.c
new file mode 100644
index 0000000..ca9b872
--- /dev/null
+++ b/libavformat/f3mdec.c
@@ -0,0 +1,359 @@
+/*
+ * F3M file format decoder
+ * Copyright (c) 2012 Nicolas George
+ *
+ * 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
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/opt.h"
+#include "f3m.h"
+#include "internal.h"
+
+struct f3m_demuxer {
+    AVClass *class;
+    AVIOContext *pb;
+    unsigned cur_fragment;
+    unsigned cur_index_entry;
+    unsigned nb_packets_left;
+    unsigned nb_fragments;
+    uint32_t nb_index_entries;
+    uint32_t cur_dts;
+    struct f3m_index_entry {
+        unsigned nb_packets;
+    } *index;
+};
+
+static const AVOption f3m_options[] = {
+    { NULL },
+};
+
+#define MAKE_READ_INT(n) \
+static int read_uint##n(AVIOContext *pb, uint##n##_t *ret)             \
+{                                                                      \
+    union { uint##n##_t align; uint8_t b[sizeof(*ret)]; } buf;         \
+    int read = avio_read(pb, buf.b, sizeof(*ret));                     \
+    if (read < (int)sizeof(*ret))                                      \
+        return read < 0 ? read : AVERROR_EOF;                          \
+    *ret = AV_RB##n(buf.b);                                            \
+    return 0;                                                          \
+}
+MAKE_READ_INT(8)
+MAKE_READ_INT(16)
+MAKE_READ_INT(32)
+MAKE_READ_INT(64)
+
+#define FAIL(code) do { ret = (code); goto fail; } while (0)
+
+static AVCodec *find_decoder(const char *name, enum CodecID *id)
+{
+    AVCodec *c;
+
+    c = avcodec_find_decoder_by_name(name);
+    if (!c)
+        c = avcodec_find_encoder_by_name(name);
+    if (!c)
+        while ((c = av_codec_next(c)) &&
+               strcmp(name, c->name) &&
+               strcmp(name, avcodec_get_name(c->id)));
+    if (c) {
+        *id = c->id;
+        if (!av_codec_is_decoder(c))
+            c = avcodec_find_decoder(c->id);
+    } else {
+        *id = CODEC_ID_NONE;
+    }
+    return c;
+}
+
+static int read_block(AVIOContext *pb, uint8_t **data, uint32_t *size)
+{
+    int ret;
+    uint32_t s;
+    uint8_t *d;
+
+    if ((ret = read_uint32(pb, &s)) < 0)
+        return ret;
+    if (!(d = av_malloc(s)))
+        return AVERROR(ENOMEM);
+    if ((ret = avio_read(pb, d, s)) < s) {
+        av_free(d);
+        return AVERROR_EOF;
+    }
+    *data = d;
+    *size = s;
+    return 0;
+}
+
+static void read_string(uint8_t **p, uint8_t *end, const char **ret)
+{
+    char *q = memchr(*p, 0, end - *p);
+    if (!q) {
+        *p = end;
+        *ret = "";
+    } else {
+        *ret = *p;
+        *p = q + 1;
+    }
+}
+
+static int read_string_section(AVFormatContext *avf,
+                               char **first, AVDictionary **dict)
+{
+    const char *key, *val;
+    uint32_t len = 0;
+    uint8_t *buf, *end, *p;
+    int ret;
+
+    if ((ret = read_block(avf->pb, &buf, &len)) < 0)
+        return ret;
+    p = buf;
+    end = buf + len;
+    if (first) {
+        read_string(&p, end, &val);
+        if (!(*first = av_strdup(val)))
+            FAIL(AVERROR(ENOMEM));
+    }
+    *dict = NULL;
+    while (p < end) {
+        read_string(&p, end, &key);
+        read_string(&p, end, &val);
+        if ((ret = av_dict_set(dict, key, val, 0)) < 0)
+            FAIL(ret);
+    }
+    ret = 0;
+fail:
+    av_free(buf);
+    return ret;
+}
+
+static int use_index_entry(AVFormatContext *avf, unsigned index)
+{
+    struct f3m_demuxer *f3m = avf->priv_data;
+    unsigned fragment_num, offset;
+    AVIndexEntry *entry;
+    int ret;
+
+    if (index >= f3m->nb_index_entries)
+        return AVERROR_EOF;
+    entry = &avf->streams[0]->index_entries[index];
+    fragment_num = entry->pos >> 32;
+    offset       = entry->pos & 0xFFFFFFFF;
+    if (fragment_num != f3m->cur_fragment) {
+        ret = ff_f3m_open_fragment(avf, &f3m->pb, &f3m->cur_fragment,
+                                   fragment_num, AVIO_FLAG_READ);
+        if (ret < 0)
+            return ret;
+    }
+    if ((ret = avio_seek(f3m->pb, offset, SEEK_SET)) < 0)
+        return ret;
+    f3m->nb_packets_left = f3m->index[index].nb_packets;
+    f3m->cur_index_entry = index;
+    f3m->cur_dts         = entry->timestamp;
+    return 0;
+}
+
+static int f3m_read_probe(AVProbeData *data)
+{
+    return ff_f3m_check_filename(data->filename) &&
+           data->buf_size >= 8 && !memcmp(data->buf, F3M_MAGIC, 8) ?
+           AVPROBE_SCORE_MAX : 0;
+}
+
+static int f3m_read_header(AVFormatContext *avf)
+{
+    struct f3m_demuxer *f3m = avf->priv_data;
+    int ret;
+    uint32_t i, j;
+    uint8_t magic[8];
+    uint8_t nb_streams, codec_type, *extradata;
+    AVDictionary *metadata;
+    uint64_t stream_bytes, stream_packets;
+    uint32_t stream_dur, extradata_size;
+    uint32_t entry_dts, entry_offset;
+    uint16_t fragment_num, nb_packets;
+    int64_t offset;
+    char *codec_name;
+    enum CodecID codec_id;
+    AVCodec *codec;
+    AVStream *st;
+
+    if (!ff_f3m_check_filename(avf->filename)) {
+        av_log(avf, AV_LOG_ERROR, "Invalid F3M filename\n");
+        return AVERROR(EINVAL);
+    }
+    if (avio_read(avf->pb, magic, sizeof(magic)) != sizeof(magic) ||
+        memcmp(magic, F3M_MAGIC, sizeof(magic)))
+        return AVERROR_INVALIDDATA;
+    if ((ret = read_uint8(avf->pb, &nb_streams)) < 0)
+        return ret;
+    if (ret > 127)
+        return AVERROR_INVALIDDATA;
+    if ((ret = read_string_section(avf, NULL, &metadata)) < 0)
+        return ret;
+    avf->metadata = metadata;
+    metadata = NULL;
+
+    for (i = 0; i < nb_streams; i++) {
+        if ((ret = read_uint8 (avf->pb, &codec_type    )) < 0 ||
+            (ret = read_uint64(avf->pb, &stream_bytes  )) < 0 ||
+            (ret = read_uint64(avf->pb, &stream_packets)) < 0 ||
+            (ret = read_uint32(avf->pb, &stream_dur    )) < 0 ||
+            (ret = read_string_section(avf, &codec_name, &metadata)) < 0 ||
+            (ret = read_block(avf->pb, &extradata, &extradata_size) < 0))
+            FAIL(ret);
+
+        if (codec_type >= AVMEDIA_TYPE_NB)
+            FAIL(AVERROR_INVALIDDATA);
+        codec = find_decoder(codec_name, &codec_id);
+        av_freep(&codec_name);
+        st = avformat_new_stream(avf, codec);
+        av_assert1(st->index == i);
+
+        st->codec->codec_type     = codec_type;
+        st->codec->codec_id       = codec_id;
+        st->codec->extradata      = extradata;
+        st->codec->extradata_size = extradata_size;
+        if (stream_dur)
+            st->codec->bit_rate = av_rescale(stream_bytes, 8000, stream_dur);
+        avpriv_set_pts_info(st, 32, 1, 1000);
+        st->duration  = stream_dur;
+        st->nb_frames = stream_packets;
+        st->metadata = metadata;
+        metadata = NULL;
+    }
+
+    if ((ret = read_uint32(avf->pb, &f3m->nb_index_entries)) < 0 ||
+        (ret = read_uint16(avf->pb, &fragment_num         )) < 0)
+        FAIL(ret);
+    if (!f3m->nb_index_entries)
+        return AVERROR_INVALIDDATA;
+    f3m->nb_fragments = fragment_num + 1;
+    if (!(f3m->index = av_calloc(f3m->nb_index_entries, sizeof(*f3m->index))))
+        FAIL(AVERROR(ENOMEM));
+
+    for (i = 0; i < f3m->nb_index_entries; i++) {
+        if ((ret = read_uint32(avf->pb, &entry_dts   )) < 0 ||
+            (ret = read_uint16(avf->pb, &fragment_num)) < 0 ||
+            (ret = read_uint32(avf->pb, &entry_offset)) < 0)
+            FAIL(ret);
+        if (fragment_num >= f3m->nb_fragments)
+            FAIL(AVERROR_INVALIDDATA);
+        avio_skip(avf->pb, avf->nb_streams * 4); /* octets in each stream */
+        for (j = 0; j < avf->nb_streams; j++) {
+            if ((ret = read_uint16(avf->pb, &nb_packets)) < 0)
+                FAIL(ret);
+            f3m->index[i].nb_packets += nb_packets;
+        }
+        if (i == 0)
+            avf->start_time = f3m->cur_dts = entry_dts;
+        offset = ((uint64_t)fragment_num << 32) + entry_offset;
+        av_add_index_entry(avf->streams[0], offset, entry_dts,
+                           0, 0, AVINDEX_KEYFRAME);
+    }
+
+    f3m->cur_fragment = -1;
+    if ((ret = use_index_entry(avf, 0)) < 0)
+        return ret;
+
+    return 0;
+
+fail:
+    av_freep(&codec_name);
+    av_dict_free(&metadata);
+    av_freep(&f3m->index);
+    return ret;
+}
+
+static int f3m_read_close(AVFormatContext *avf)
+{
+    struct f3m_demuxer *f3m = avf->priv_data;
+
+    av_freep(&f3m->index);
+    avio_close(f3m->pb);
+    f3m->pb = NULL;
+    return 0;
+}
+
+static int f3m_read_packet(AVFormatContext *avf, AVPacket *pkt)
+{
+    struct f3m_demuxer *f3m = avf->priv_data;
+    uint32_t size;
+    uint16_t ddts, dpts;
+    unsigned stream_index;
+    int ret;
+
+    if (!f3m->nb_packets_left)
+        if ((ret = use_index_entry(avf, f3m->cur_index_entry + 1)) < 0)
+            return ret;
+    f3m->nb_packets_left--;
+    if ((ret = read_uint32(f3m->pb, &size)) < 0 ||
+        (ret = read_uint16(f3m->pb, &dpts)) < 0)
+        return ret;
+    stream_index = size >> 24;
+    size &= 0xFFFFFF;
+    if ((ret = av_new_packet(pkt, size)) < 0)
+        return ret;
+    if ((ret = avio_read(f3m->pb, pkt->data, size)) < (int)size ||
+        (ret = read_uint16(f3m->pb, &ddts)) < 0) {
+        av_log(avf, AV_LOG_WARNING, "Truncated packet\n");
+        av_destruct_packet(pkt);
+        return ret < 0 ? ret : AVERROR_EOF;
+    }
+    pkt->stream_index = stream_index & 0x7F;
+    pkt->pos          = -1;
+    pkt->flags        = (stream_index & 0x80) ? AV_PKT_FLAG_KEY : 0;
+    pkt->dts          = f3m->cur_dts;
+    pkt->pts          = f3m->cur_dts + dpts;
+    f3m->cur_dts += ddts;
+    return 0;
+}
+
+static int f3m_read_seek(AVFormatContext *avf, int stream, int64_t ts,
+                         int flags)
+{
+    int index;
+
+    if (stream)
+        return AVERROR(EINVAL);
+    if ((index = av_index_search_timestamp(avf->streams[0], ts, flags)) < 0)
+        return index;
+    return use_index_entry(avf, index);
+}
+
+
+static const AVClass f3m_demuxer_class = {
+    .class_name = "f3m_demuxer",
+    .item_name  = av_default_item_name,
+    .option     = f3m_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+AVInputFormat ff_f3m_demuxer = {
+    .name           = "f3m",
+    .long_name      = NULL_IF_CONFIG_SMALL("FFmpeg Fragmented Multimedia"),
+    .priv_data_size = sizeof(struct f3m_demuxer),
+    .read_probe     = f3m_read_probe,
+    .read_header    = f3m_read_header,
+    .read_close     = f3m_read_close,
+    .read_packet    = f3m_read_packet,
+    .read_seek      = f3m_read_seek,
+    .read_seek2     = NULL, //f3m_read_seek2,
+    .extensions     = "f3m",
+    .priv_class     = &f3m_demuxer_class,
+};
diff --git a/libavformat/f3menc.c b/libavformat/f3menc.c
new file mode 100644
index 0000000..cf9810b
--- /dev/null
+++ b/libavformat/f3menc.c
@@ -0,0 +1,316 @@
+/*
+ * F3M file format encoder
+ * Copyright (c) 2012 Nicolas George
+ *
+ * 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
+ */
+
+#include "libavutil/opt.h"
+#include "avformat.h"
+#include "f3m.h"
+#include "internal.h"
+
+#include "libavutil/timestamp.h"
+
+struct f3m_muxer {
+    AVClass *class;
+    AVIOContext *pb;
+    int64_t index_offset;
+    int64_t last_dts;
+    int64_t fragment_first_dts;
+    unsigned fragment_duration;
+    unsigned fragment_size;
+    unsigned cur_fragment;
+    unsigned nb_index_entries;
+    int skip_to_ref;
+    struct f3m_stream {
+        int64_t head_offset;
+        int64_t octets;
+        int64_t packets;
+        int64_t last_index_octets;
+        int64_t last_index_packets;
+        int32_t next_dts;
+    } *st;
+};
+
+static const AVOption f3m_options[] = {
+    { "fragment_duration", "set the approximate maximum duration of a fragment",
+      offsetof(struct f3m_muxer, fragment_duration),
+      AV_OPT_TYPE_INT, { .dbl = 60000 }, 0, INT_MAX,
+      AV_OPT_FLAG_ENCODING_PARAM },
+    { "fragment_size", "set the approximate maximum size of a fragment",
+      offsetof(struct f3m_muxer, fragment_size),
+      AV_OPT_TYPE_INT, { .dbl = 256 * 1024 * 1024 }, 0, INT_MAX,
+      AV_OPT_FLAG_ENCODING_PARAM },
+    { "skip_to_ref",
+      "skip all frames before the first keyframe on the reference stream",
+      offsetof(struct f3m_muxer, skip_to_ref),
+      AV_OPT_TYPE_INT, { .dbl = 0 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
+    { NULL },
+};
+
+#define FAIL(code) do { ret = (code); goto fail; } while (0)
+
+static int count_string_length(uint32_t *len, const char *str)
+{
+    size_t l = strlen(str);
+    if (*len == UINT32_MAX || l > UINT32_MAX - *len - 1)
+        return AVERROR_BUFFER_TOO_SMALL;
+    *len += l + 1;
+    return 0;
+}
+
+static int write_string_section(AVFormatContext *avf,
+                                const char *first, AVDictionary *dict)
+{
+    uint32_t len = 0;
+    int ret;
+    AVDictionaryEntry *entry = NULL;
+
+    if (first)
+        if ((ret = count_string_length(&len, first)) < 0)
+            return ret;
+    while ((entry = av_dict_get(dict, "", entry, AV_DICT_IGNORE_SUFFIX)))
+        if ((ret = count_string_length(&len, entry->key  )) < 0 ||
+            (ret = count_string_length(&len, entry->value)) < 0)
+            return ret;
+    entry = NULL;
+    avio_wb32(avf->pb, len);
+    if (first)
+        avio_write(avf->pb, first, strlen(first) + 1);
+    entry = NULL;
+    while ((entry = av_dict_get(dict, "", entry, AV_DICT_IGNORE_SUFFIX))) {
+        avio_write(avf->pb, entry->key,   strlen(entry->key)   + 1); 
+        avio_write(avf->pb, entry->value, strlen(entry->value) + 1);
+    }
+    return 0;
+}
+
+static int f3m_write_header(AVFormatContext *avf)
+{
+    struct f3m_muxer *f3m = avf->priv_data;
+    int i, ret;
+
+    if (!avf->pb->seekable) {
+        av_log(avf, AV_LOG_ERROR, "Muxer needs seeking\n");
+        FAIL(AVERROR_PATCHWELCOME);
+    }
+    if (!ff_f3m_check_filename(avf->filename)) {
+        av_log(avf, AV_LOG_ERROR, "Invalid F3M filename\n");
+        FAIL(AVERROR(EINVAL));
+    }
+    if (!avf->nb_streams || avf->nb_streams > 127) {
+        av_log(avf, AV_LOG_ERROR, "Invalid number of streams\n");
+        FAIL(AVERROR(EINVAL));
+    }
+    if (avf->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO) {
+        av_log(avf, AV_LOG_ERROR, "First stream must be video\n");
+        FAIL(AVERROR(EINVAL));
+    }
+
+    if (!(f3m->st = av_calloc(avf->nb_streams, sizeof(*f3m->st))))
+        FAIL(AVERROR(ENOMEM));
+
+    avio_write(avf->pb, F3M_MAGIC, 8);
+    avio_w8(avf->pb, avf->nb_streams);
+    if ((ret = write_string_section(avf, NULL, avf->metadata)) < 0)
+        FAIL(ret);
+
+    for (i = 0; i < avf->nb_streams; i++) {
+        AVStream *st = avf->streams[i];
+
+        f3m->st[i].head_offset = avio_tell(avf->pb);
+        avio_w8(avf->pb, st->codec->codec_type);
+        avio_wb64(avf->pb, 0);
+        avio_wb64(avf->pb, 0);
+        avio_wb32(avf->pb, 0);
+        ret = write_string_section(avf, avcodec_get_name(st->codec->codec_id),
+                                   st->metadata);
+        if (ret < 0)
+            FAIL(ret);
+        avio_wb32(avf->pb, st->codec->extradata_size);
+        avio_write(avf->pb, st->codec->extradata, st->codec->extradata_size);
+        avpriv_set_pts_info(st, 32, 1, 1000);
+    }
+
+    f3m->index_offset = avio_tell(avf->pb);
+    avio_wb32(avf->pb, 0);      /* number of index entries */
+    avio_wb16(avf->pb, 0xFFFF); /* last fragment */
+    avio_flush(avf->pb);
+
+    ret = ff_f3m_open_fragment(avf, &f3m->pb, &f3m->cur_fragment, 0,
+                               AVIO_FLAG_WRITE);
+    if (ret < 0)
+        FAIL(AVERROR(EIO));
+
+    return 0;
+
+fail:
+    av_freep(&f3m->st);
+    return ret;
+}
+
+static void finish_packet(AVFormatContext *avf, int64_t next_dts)
+{
+    struct f3m_muxer *f3m = avf->priv_data;
+
+    if (!f3m->nb_index_entries)
+        return;
+    avio_wb16(f3m->pb, next_dts - f3m->last_dts);
+    f3m->last_dts = next_dts;
+}
+
+static void finish_index_entry(AVFormatContext *avf)
+{
+    struct f3m_muxer *f3m = avf->priv_data;
+    int i;
+
+    if (!f3m->nb_index_entries)
+        return;
+    for (i = 0; i < avf->nb_streams; i++) {
+        avio_wb32(avf->pb, f3m->st[i].octets - f3m->st[i].last_index_octets);
+        f3m->st[i].last_index_octets = f3m->st[i].octets;
+    }
+    for (i = 0; i < avf->nb_streams; i++) {
+        avio_wb16(avf->pb, f3m->st[i].packets - f3m->st[i].last_index_packets);
+        f3m->st[i].last_index_packets = f3m->st[i].packets;
+    }
+    avio_flush(avf->pb);
+}
+
+static int f3m_write_trailer(AVFormatContext *avf)
+{
+    struct f3m_muxer *f3m = avf->priv_data;
+    int i;
+    int64_t offset;
+    uint32_t last_dts = f3m->st[0].next_dts;
+
+    for (i = 1; i < avf->nb_streams; i++)
+        if (f3m->st[i].next_dts > f3m->last_dts)
+            last_dts = FFMIN(last_dts, f3m->st[i].next_dts);
+    last_dts = FFMAX(last_dts, f3m->last_dts);
+    finish_packet(avf, f3m->last_dts);
+    avio_flush(f3m->pb);
+    finish_index_entry(avf);
+    avio_close(f3m->pb);
+    f3m->pb = NULL;
+
+    if (avio_seek(avf->pb, f3m->index_offset, SEEK_SET) != f3m->index_offset)
+        return AVERROR(EIO);
+    avio_wb32(avf->pb, f3m->nb_index_entries);
+    avio_wb16(avf->pb, f3m->cur_fragment);
+
+    for (i = 0; i < avf->nb_streams; i++) {
+        offset = f3m->st[i].head_offset + 1;
+        if (avio_seek(avf->pb, offset, SEEK_SET) != offset)
+            return AVERROR(EIO);
+        avio_wb64(avf->pb, f3m->st[i].octets);
+        avio_wb64(avf->pb, f3m->st[i].packets);
+        avio_wb32(avf->pb, f3m->st[i].next_dts);
+    }
+
+    av_freep(&f3m->st);
+    return 0;
+}
+
+static int write_index_entry(AVFormatContext *avf, int64_t dts)
+{
+    struct f3m_muxer *f3m = avf->priv_data;
+    int32_t offset;
+    int64_t duration;
+    int ret;
+
+    finish_index_entry(avf);
+    if (!f3m->nb_index_entries)
+        f3m->fragment_first_dts = dts;
+
+    offset = avio_tell(f3m->pb);
+    duration = dts - f3m->fragment_first_dts;
+    if ((f3m->fragment_duration && duration >= f3m->fragment_duration) ||
+        (f3m->fragment_size     && offset   >= f3m->fragment_size    )) {
+        if (f3m->cur_fragment == UINT16_MAX) {
+            av_log(avf, AV_LOG_ERROR, "Too many fragments\n");
+            return AVERROR(EINVAL);
+        }
+        avio_flush(f3m->pb);
+        ret = ff_f3m_open_fragment(avf, &f3m->pb, &f3m->cur_fragment,
+                                   f3m->cur_fragment + 1, AVIO_FLAG_WRITE);
+        f3m->fragment_first_dts = dts;
+        if (ret < 0)
+            return ret;
+    }
+
+    avio_wb32(avf->pb, dts);
+    avio_wb16(avf->pb, f3m->cur_fragment);
+    avio_wb32(avf->pb, avio_tell(f3m->pb));
+    f3m->nb_index_entries++;
+    return 0;
+}
+
+static int f3m_write_packet(AVFormatContext *avf, AVPacket *pkt)
+{
+    struct f3m_muxer *f3m = avf->priv_data;
+    int is_ref_frame = pkt->stream_index == 0 && (pkt->flags & AV_PKT_FLAG_KEY);
+    int ret;
+
+    if (pkt->dts == AV_NOPTS_VALUE || pkt->pts == AV_NOPTS_VALUE)
+        return AVERROR(EINVAL);
+    if (pkt->size > 0xFFFFFF) {
+        av_log(avf, AV_LOG_ERROR, "Packet too big\n");
+        return AVERROR(EINVAL);
+    }
+    finish_packet(avf, pkt->dts);
+
+    if (!f3m->nb_index_entries || is_ref_frame) {
+        if (f3m->skip_to_ref && !is_ref_frame)
+            return 0;
+        if ((ret = write_index_entry(avf, pkt->dts)) < 0)
+            return ret;
+    }
+
+    f3m->st[pkt->stream_index].octets += pkt->size;
+    f3m->st[pkt->stream_index].packets++;
+    f3m->st[pkt->stream_index].next_dts = pkt->dts + pkt->duration;
+
+    avio_w8(f3m->pb, pkt->stream_index |
+                     ((pkt->flags & AV_PKT_FLAG_KEY) ? 0x80 : 0));
+    avio_wb24(f3m->pb, pkt->size);
+    avio_wb16(f3m->pb, pkt->pts - pkt->dts);
+    avio_write(f3m->pb, pkt->data, pkt->size);
+    f3m->last_dts = pkt->dts;
+    avio_flush(f3m->pb);
+    return 0;
+}
+
+static const AVClass f3m_muxer_class = {
+    .class_name = "f3m_muxer",
+    .item_name  = av_default_item_name,
+    .option     = f3m_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+AVOutputFormat ff_f3m_muxer = {
+    .name           = "f3m",
+    .long_name      = NULL_IF_CONFIG_SMALL("FFmpeg Fragmented Multimedia"),
+    .priv_data_size = sizeof(struct f3m_muxer),
+    .flags          = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS,
+    .write_header   = f3m_write_header,
+    .write_trailer  = f3m_write_trailer,
+    .write_packet   = f3m_write_packet,
+    .extensions     = "f3m",
+    .priv_class     = &f3m_muxer_class,
+};
-- 
1.7.10.4



More information about the ffmpeg-devel mailing list