[11/11] Add an f4findex tool

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

Commit Message

Martin Storsjö Oct. 9, 2013, 12:03 p.m.
This tools creates all the extra manifest files necessary for
serving an F4F file using Adobe servers.
---
I'm not sure if anybody actually is interested in this, but I've
implemented it for completeness sake.
---
 Changelog            |    1 +
 libavformat/Makefile |    1 +
 tools/f4findex.c     |  363 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 365 insertions(+)
 create mode 100644 tools/f4findex.c

Comments

Derek Buitenhuis Oct. 11, 2013, 12:49 p.m. | #1
On 10/9/2013 1:03 PM, Martin Storsjö wrote:
> +    av_register_all();

Doesn't it also technically need avcodec_register_all(),
since you are calling avformat_find_stream_info()? Or
maybe it calls it on its own?

- Derek
Martin Storsjö Oct. 11, 2013, 12:55 p.m. | #2
On Fri, 11 Oct 2013, Derek Buitenhuis wrote:

> On 10/9/2013 1:03 PM, Martin Storsjö wrote:
>> +    av_register_all();
>
> Doesn't it also technically need avcodec_register_all(),
> since you are calling avformat_find_stream_info()? Or
> maybe it calls it on its own?

To quote yourself from 7 minutes earlier, "Why waste others' time to 
answer questions you can easily answer on your own?" - 
libavformat/allformats.c - yes it chain initializes avcodec as well. (If 
the docs doesn't say that it perhaps should be revised.)

// Martin
Derek Buitenhuis Oct. 11, 2013, 12:58 p.m. | #3
On 10/11/2013 1:55 PM, Martin Storsjö wrote:
> To quote yourself from 7 minutes earlier, "Why waste others' time to 
> answer questions you can easily answer on your own?" - 
> libavformat/allformats.c - yes it chain initializes avcodec as well. (If 
> the docs doesn't say that it perhaps should be revised.)

Excuse me while I eat a piece of humble pie.

- Derek

Patch

diff --git a/Changelog b/Changelog
index 1fcbf81..02f17bb 100644
--- a/Changelog
+++ b/Changelog
@@ -39,6 +39,7 @@  version 10:
 - mux chapters in ASF files
 - F4V muxer
 - Live HDS muxer
+- F4F muxer and manifest writing tool
 
 
 version 9:
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 8661b2f..12012d4 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -396,6 +396,7 @@  TESTPROGS = seek                                                        \
 TESTPROGS-$(CONFIG_NETWORK)              += noproxy
 
 TOOLS     = aviocat                                                     \
+            f4findex                                                    \
             ismindex                                                    \
             pktdumper                                                   \
             probetest                                                   \
diff --git a/tools/f4findex.c b/tools/f4findex.c
new file mode 100644
index 0000000..d733018
--- /dev/null
+++ b/tools/f4findex.c
@@ -0,0 +1,363 @@ 
+/*
+ * Copyright (c) 2013 Martin Storsjo
+ *
+ * This file is part of Libav.
+ *
+ * Libav 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.
+ *
+ * Libav 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 Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "libavformat/avformat.h"
+#include "libavutil/avstring.h"
+#include "libavutil/base64.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mathematics.h"
+
+static int usage(const char *argv0, int ret)
+{
+    fprintf(stderr, "%s -n basename file1 [file2] ...\n", argv0);
+    return ret;
+}
+
+struct AfraOffset {
+    int64_t time;
+    int64_t offset;
+};
+
+struct Track {
+    char basename[1000];
+    int bitrate;
+    struct AfraOffset *afra;
+    int nb_afra;
+
+    uint8_t *metadata;
+    int metadata_size;
+    uint8_t *abst;
+    int abst_size;
+};
+
+struct Tracks {
+    int nb_tracks;
+    float duration;
+    struct Track **tracks;
+};
+
+static int read_afra(struct Track *track, AVIOContext *f)
+{
+    if (av_reallocp_array(&track->afra, track->nb_afra + 1,
+                          sizeof(*track->afra)) < 0) {
+        track->nb_afra = 0;
+        return AVERROR(ENOMEM);
+    }
+    track->afra[track->nb_afra].offset = avio_tell(f) - 8;
+    avio_seek(f, 4 + 1 + 4 + 1, SEEK_CUR);
+    track->afra[track->nb_afra].time = avio_rb64(f);
+    track->nb_afra++;
+    return 0;
+}
+
+static int read_f4f(struct Track *track, const char *file)
+{
+    int err = 0;
+    AVIOContext *f = NULL;
+
+    if ((err = avio_open2(&f, file, AVIO_FLAG_READ, NULL, NULL)) < 0)
+        goto fail;
+    while (1) {
+        uint32_t size = avio_rb32(f);
+        uint32_t tag = avio_rl32(f);
+        if (f->eof_reached)
+            break;
+        if (tag == MKTAG('a', 'f', 'r', 'a')) {
+            int64_t end = avio_tell(f) + size - 8;
+            read_afra(track, f);
+            avio_seek(f, end, SEEK_SET);
+        } else if (tag == MKTAG('a', 'b', 's', 't')) {
+            avio_seek(f, -8, SEEK_CUR);
+            av_freep(&track->abst);
+            track->abst_size = 0;
+            track->abst = av_malloc(size);
+            if (!track->abst) {
+                err = AVERROR(ENOMEM);
+                goto fail;
+            }
+            avio_read(f, track->abst, size);
+            track->abst_size = size;
+        } else {
+            avio_seek(f, size - 8, SEEK_CUR);
+        }
+    }
+
+fail:
+    if (f)
+        avio_close(f);
+    if (err)
+        fprintf(stderr, "Unable to read the MFRA atom in %s\n", file);
+    return err;
+}
+
+static int parse_header(void *opaque, uint8_t *buf, int buf_size)
+{
+    struct Track *track = opaque;
+    if (buf_size < 13)
+        return AVERROR_INVALIDDATA;
+    if (memcmp(buf, "FLV", 3))
+        return AVERROR_INVALIDDATA;
+    buf += 13;
+    buf_size -= 13;
+    while (buf_size >= 11 + 4) {
+        int type = buf[0];
+        int size = AV_RB24(&buf[1]) + 11 + 4;
+        if (size > buf_size)
+            return AVERROR_INVALIDDATA;
+        if (type == 0x12) {
+            if (track->metadata)
+                return AVERROR_INVALIDDATA;
+            track->metadata_size = size - 11 - 4;
+            track->metadata = av_malloc(track->metadata_size);
+            if (!track->metadata)
+                return AVERROR(ENOMEM);
+            memcpy(track->metadata, buf + 11, track->metadata_size);
+        }
+        buf += size;
+        buf_size -= size;
+    }
+    if (!track->metadata)
+        return AVERROR_INVALIDDATA;
+    return buf_size;
+}
+
+static int write_f4x(struct Track *track, const char *file)
+{
+    char namebuf[200];
+    AVIOContext *pb;
+    int err, i;
+    int64_t len;
+
+    av_strlcpy(namebuf, file, sizeof(namebuf));
+    namebuf[strlen(namebuf) - 1] = 'x';
+
+    if ((err = avio_open2(&pb, namebuf, AVIO_FLAG_WRITE, NULL, NULL)) < 0)
+        return err;
+
+    avio_wb32(pb, 0); /* size placeholder */
+    avio_wl32(pb, MKTAG('a', 'f', 'r', 'a'));
+    avio_wb32(pb, 0); // version + flags
+    avio_w8(pb, 0xe0); // long ids, long offsets, no global entries
+    avio_wb32(pb, 1000); // timescale
+    avio_wb32(pb, 0); // entry count
+    avio_wb32(pb, track->nb_afra); // global entry count
+    for (i = 0; i < track->nb_afra; i++) {
+        avio_wb64(pb, track->afra[i].time);
+        avio_wb32(pb, 1);
+        avio_wb32(pb, i + 1);
+        avio_wb64(pb, track->afra[i].offset);
+        avio_wb64(pb, 0);
+    }
+    len = avio_tell(pb);
+    avio_seek(pb, 0, SEEK_SET);
+    avio_wb32(pb, len);
+    avio_seek(pb, len, SEEK_SET);
+    avio_flush(pb);
+    avio_close(pb);
+
+    return 0;
+}
+
+static int remove_suffix(char *str, const char *suffix)
+{
+    int len = strlen(str);
+    int suffix_len = strlen(suffix);
+    if (len <= suffix_len)
+        return -1;
+    if (strcmp(&str[len - suffix_len], suffix))
+        return -1;
+    str[len - suffix_len] = '\0';
+    return 0;
+}
+
+static int handle_file(struct Tracks *tracks, const char *file)
+{
+    AVFormatContext *ctx = NULL;
+    int err = 0, i;
+    char errbuf[50];
+    AVFormatContext *out;
+    struct Track *track;
+    uint8_t iobuf[32768];
+
+    track = av_mallocz(sizeof(*track));
+    if (!track) {
+        err = AVERROR(ENOMEM);
+        goto fail;
+    }
+    if (av_reallocp_array(&tracks->tracks, tracks->nb_tracks + 1,
+                          sizeof(*tracks->tracks)) < 0) {
+        tracks->nb_tracks = 0;
+        err = AVERROR(ENOMEM);
+        goto fail;
+    }
+    tracks->tracks[tracks->nb_tracks] = track;
+    tracks->nb_tracks++;
+
+    av_strlcpy(track->basename, av_basename(file), sizeof(track->basename));
+    if (remove_suffix(track->basename, "Seg1.f4f")) {
+        av_log(NULL, AV_LOG_ERROR, "File name %s doesn't end with %s\n",
+               track->basename, "Seg1.f4f");
+        err = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    err = avformat_open_input(&ctx, file, NULL, NULL);
+    if (err < 0) {
+        av_strerror(err, errbuf, sizeof(errbuf));
+        fprintf(stderr, "Unable to open %s: %s\n", file, errbuf);
+        return 1;
+    }
+
+    err = avformat_find_stream_info(ctx, NULL);
+    if (err < 0) {
+        av_strerror(err, errbuf, sizeof(errbuf));
+        fprintf(stderr, "Unable to identify %s: %s\n", file, errbuf);
+        goto fail;
+    }
+    tracks->duration = av_q2d(AV_TIME_BASE_Q) * ctx->duration;
+
+    out = avformat_alloc_context();
+    out->oformat = av_guess_format("flv", NULL, NULL);
+    out->pb = avio_alloc_context(iobuf, sizeof(iobuf), AVIO_FLAG_WRITE, track,
+                                 NULL, parse_header, NULL);
+    for (i = 0; i < ctx->nb_streams; i++) {
+        AVStream *st = avformat_new_stream(out, NULL);
+        avcodec_copy_context(st->codec, ctx->streams[i]->codec);
+        st->codec->codec_tag = 0;
+        st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
+        track->bitrate += ctx->streams[i]->codec->bit_rate;
+        if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+            st->codec->time_base.num = 1;
+            st->codec->time_base.den = 30;
+        }
+    }
+    avformat_close_input(&ctx);
+    ctx = NULL;
+
+    err = avformat_write_header(out, NULL);
+    avio_flush(out->pb);
+    av_free(out->pb);
+    avformat_free_context(out);
+
+    err = read_f4f(track, file);
+
+    write_f4x(track, file);
+
+fail:
+    if (ctx)
+        avformat_close_input(&ctx);
+    return err;
+}
+
+static void clean_tracks(struct Tracks *tracks)
+{
+    int i;
+    for (i = 0; i < tracks->nb_tracks; i++) {
+        av_freep(&tracks->tracks[i]->afra);
+        av_freep(&tracks->tracks[i]->metadata);
+        av_freep(&tracks->tracks[i]->abst);
+        av_freep(&tracks->tracks[i]);
+    }
+    av_freep(&tracks->tracks);
+    tracks->nb_tracks = 0;
+}
+
+static int write_manifest(struct Tracks *tracks, const char *name)
+{
+    AVIOContext *out;
+    char filename[1024];
+    int ret, i;
+
+    snprintf(filename, sizeof(filename), "%s.f4m", name);
+    ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, NULL, NULL);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "Unable to open %s for writing\n", filename);
+        return ret;
+    }
+    avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+    avio_printf(out, "<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n");
+    avio_printf(out, "\t<id>%s</id>\n", name);
+    avio_printf(out, "\t<streamType>%s</streamType>\n", "recorded");
+    avio_printf(out, "\t<deliveryType>streaming</deliveryType>\n");
+    avio_printf(out, "\t<duration>%f</duration>\n", tracks->duration);
+    for (i = 0; i < tracks->nb_tracks; i++) {
+        struct Track *track = tracks->tracks[i];
+        int b64_size = AV_BASE64_SIZE(track->abst_size);
+        char *base64 = av_malloc(b64_size);
+        if (!base64) {
+            avio_close(out);
+            return AVERROR(ENOMEM);
+        }
+        av_base64_encode(base64, b64_size, track->abst, track->abst_size);
+
+        avio_printf(out, "\t<bootstrapInfo profile=\"named\" id=\"bootstrap%d\">\n", i);
+        avio_printf(out, "\t\t%s\n", base64);
+        avio_printf(out, "\t</bootstrapInfo>\n");
+
+        av_free(base64);
+        b64_size = AV_BASE64_SIZE(track->metadata_size);
+        base64 = av_malloc(b64_size);
+        if (!base64) {
+            avio_close(out);
+            return AVERROR(ENOMEM);
+        }
+        av_base64_encode(base64, b64_size, track->metadata, track->metadata_size);
+        avio_printf(out, "\t<media bitrate=\"%d\" url=\"%s\" bootstrapInfoId=\"bootstrap%d\">\n",
+                    track->bitrate/1000, track->basename, i);
+        avio_printf(out, "\t\t<metadata>%s</metadata>\n", base64);
+        avio_printf(out, "\t</media>\n");
+        av_free(base64);
+    }
+    avio_printf(out, "</manifest>\n");
+    avio_flush(out);
+    avio_close(out);
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    const char *basename = NULL;
+    int i;
+    struct Tracks tracks = { 0 };
+
+    av_register_all();
+
+    for (i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], "-n")) {
+            basename = argv[i + 1];
+            i++;
+        } else if (argv[i][0] == '-') {
+            return usage(argv[0], 1);
+        } else {
+            if (handle_file(&tracks, argv[i]))
+                return 1;
+        }
+    }
+    if (!tracks.nb_tracks || !basename)
+        return usage(argv[0], 1);
+
+    write_manifest(&tracks, basename);
+    clean_tracks(&tracks);
+
+    return 0;
+}