@@ -39,6 +39,7 @@ version 10:
- mux chapters in ASF files
- F4V muxer
- Live HDS muxer
+- F4F muxer and manifest writing tool
version 9:
@@ -396,6 +396,7 @@ TESTPROGS = seek \
TESTPROGS-$(CONFIG_NETWORK) += noproxy
TOOLS = aviocat \
+ f4findex \
ismindex \
pktdumper \
probetest \
new file mode 100644
@@ -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;
+}