From patchwork Wed Oct 9 12:03:30 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [11/11] Add an f4findex tool X-Patchwork-Submitter: =?utf-8?q?Martin_Storsj=C3=B6?= X-Patchwork-Id: 43045 Message-Id: <1381320210-79941-11-git-send-email-martin@martin.st> To: libav-devel@libav.org Date: Wed, 9 Oct 2013 15:03:30 +0300 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= List-Id: libav development 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 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 +#include + +#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, "\n"); + avio_printf(out, "\n"); + avio_printf(out, "\t%s\n", name); + avio_printf(out, "\t%s\n", "recorded"); + avio_printf(out, "\tstreaming\n"); + avio_printf(out, "\t%f\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\n", i); + avio_printf(out, "\t\t%s\n", base64); + avio_printf(out, "\t\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\n", + track->bitrate/1000, track->basename, i); + avio_printf(out, "\t\t%s\n", base64); + avio_printf(out, "\t\n"); + av_free(base64); + } + avio_printf(out, "\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; +}