[FFmpeg-devel] [PATCH] tools: add muxdemux_latency.

Nicolas George nicolas.george at normalesup.org
Wed Mar 13 15:51:20 CET 2013


Allows to measure the latency of a muxer-demuxer cycle.

Signed-off-by: Nicolas George <nicolas.george at normalesup.org>
---
 libavformat/Makefile     |    1 +
 tools/muxdemux_latency.c |  402 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 403 insertions(+)
 create mode 100644 tools/muxdemux_latency.c


I am not sure whether this deserves to be included, but I find it
instructive.


diff --git a/libavformat/Makefile b/libavformat/Makefile
index 73ada77..b44097e 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -448,6 +448,7 @@ TESTPROGS = noproxy                                                     \
 
 TOOLS     = aviocat                                                     \
             ismindex                                                    \
+	    muxdemux_latency                                            \
             pktdumper                                                   \
             probetest                                                   \
             seek_print                                                  \
diff --git a/tools/muxdemux_latency.c b/tools/muxdemux_latency.c
new file mode 100644
index 0000000..bf1ff5e
--- /dev/null
+++ b/tools/muxdemux_latency.c
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2013 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
+ */
+
+/*
+   Measure the latency of a muxer-demuxer cycle.
+
+   Usage:
+
+      ./tools/muxdemux_latency -i mpegts /tmp/input.h264
+
+   -> measure the latency of a MPEG-TS muxer-demuxer cycle using data
+      from /tmp/input.h264, and decoding stream information (-i)
+
+   Sample output:
+
+      mux_packet 132 772
+        write 940
+        read 940
+      demux_packet 131 750
+      mux_packet 133 766
+        write 940
+        read 940
+      demux_packet 132 778
+
+   -> the muxer was given its packet #132 with size 772;
+      in reaction it wrote 940 bytes;
+      the demuxer read 940 bytes and returned the packet #131 with size 750;
+      the muxer was given its packet #133 with size 766 and wrote 940 bytes;
+      the demuxer read 940 bytes and returned the packet #132 with size 778.
+
+      We can observe that the MPEG-TS deumuxer introduces a latency of one
+      packet.
+
+   Note: the input file must have a format similar enough to the tested
+   format, especially with regard to global headers and extradata.
+
+*/
+
+#include <stdio.h>
+#include <pthread.h>
+#include "libavutil/avassert.h"
+#include "libavformat/avformat.h"
+
+#include "config.h"
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if !HAVE_GETOPT
+#include "compat/getopt.c"
+#endif
+
+#define BUFFER_SIZE (1024 * 1024)
+
+struct async_buffer {
+    pthread_mutex_t lock;
+    pthread_cond_t cond;
+    unsigned head, tail;
+    unsigned eof;
+    unsigned read;
+    uint8_t data[BUFFER_SIZE];
+};
+
+struct context {
+    char *format;
+    struct async_buffer *abuf;
+    int do_find_info;
+    char *mux_opts;
+    char *demux_opts;
+};
+
+static void usage(int ret)
+{
+    fprintf(ret ? stderr : stdout,
+            "Usage: muxdemux_latency [options] format file\n"
+            "\t-i\tcall find_stream_info\n"
+            "\t-m opts\tmuxer options (key=val:key=val...)\n"
+            "\t-d opts\tdemuxer options (key=val:key=val...)\n"
+            );
+    exit(ret);
+}
+
+static void report_remaining_options(const char *tag, AVDictionary *opts)
+{
+    unsigned n = 0;
+    AVDictionaryEntry *e = NULL;
+
+    while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
+        fprintf(stderr, "Unknown %s option: %s\n", tag, e->key);
+        n++;
+    }
+    if (n)
+        exit(1);
+}
+
+static void report_read(struct async_buffer *abuf)
+{
+    if (abuf->read > 0) {
+        printf("  read %d\n", abuf->read);
+        abuf->read = 0;
+    }
+}
+
+static int io_read(void *opaque, uint8_t *buf, int buf_size)
+{
+    struct async_buffer *abuf = opaque;
+    int ret;
+
+    av_assert0(buf_size > 0);
+    pthread_mutex_lock(&abuf->lock);
+    while (!abuf->eof && abuf->tail == abuf->head) {
+        report_read(abuf);
+        pthread_cond_wait(&abuf->cond, &abuf->lock);
+    }
+    if (abuf->tail < abuf->head) {
+        *buf = abuf->data[abuf->tail++];
+        abuf->read++;
+        ret = 1;
+    } else {
+        report_read(abuf);
+        av_assert0(abuf->tail == abuf->head);
+        av_assert0(abuf->eof);
+        printf("  read_eof\n");
+        ret = AVERROR_EOF;
+    }
+    if (abuf->tail == abuf->head)
+        pthread_cond_signal(&abuf->cond);
+    pthread_mutex_unlock(&abuf->lock);
+    return ret;
+}
+
+static int io_write(void *opaque, uint8_t *buf, int buf_size)
+{
+    struct async_buffer *abuf = opaque;
+
+    printf("  write %d\n", buf_size);
+    if (buf_size > sizeof(abuf->data) - abuf->head) {
+        fprintf(stderr, "Buffer overflow\n");
+        exit(1);
+    }
+    memcpy(abuf->data + abuf->head, buf, buf_size);
+    abuf->head += buf_size;
+    return buf_size;
+}
+
+static AVIOContext *create_io_context(struct async_buffer *abuf, int write)
+{
+    unsigned char *buffer;
+    int buffer_size = 32768;
+    AVIOContext *io;
+
+    if (!(buffer = av_malloc(buffer_size))) {
+        fprintf(stderr, "Could not allocate I/O buffer\n");
+        exit(1);
+    }
+    io = avio_alloc_context(buffer, buffer_size, write, abuf,
+                            write ? NULL     : io_read,
+                            write ? io_write : NULL,
+                            NULL);
+    if (!io) {
+        fprintf(stderr, "Could not allocate I/O context\n");
+        exit(1);
+    }
+    return io;
+}
+
+static void open_muxer(const struct context *ctx, AVFormatContext *input,
+                       AVFormatContext **rmux)
+{
+    AVFormatContext *mux;
+    int ret, i;
+
+    ret = avformat_alloc_output_context2(&mux, NULL, ctx->format, NULL);
+    if (ret < 0) {
+        fprintf(stderr, "Could not open muxer: %s\n", av_err2str(ret));
+        exit(1);
+    }
+    if ((mux->oformat->flags & AVFMT_NOFILE)) {
+        fprintf(stderr, "Format %s does not work with a data stream\n",
+                ctx->format);
+        exit(1);
+    }
+
+    for (i = 0; i < input->nb_streams; i++) {
+        AVStream *sti = input->streams[i];
+        AVStream *sto = avformat_new_stream(mux, NULL);
+        if (!sto) {
+            fprintf(stderr, "Could not add stream\n");
+            exit(1);
+        }
+        if ((ret = avcodec_copy_context(sto->codec, sti->codec)) < 0) {
+            fprintf(stderr, "Could not copy context: %s\n", av_err2str(ret));
+            exit(1);
+        }
+        sto->sample_aspect_ratio = sto->codec->sample_aspect_ratio;
+        if ((mux->oformat->flags & AVFMT_GLOBALHEADER))
+            sto->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
+    }
+
+    mux->pb = create_io_context(ctx->abuf, 1);
+
+    *rmux = mux;
+}
+
+static void *demuxer_thread(void *opaque)
+{
+    const struct context *ctx = opaque;
+    AVFormatContext *demux;
+    AVInputFormat *format;
+    AVDictionary *opts = NULL;
+    AVPacket packet;
+    unsigned count = 0;
+    int ret;
+
+    if (!(demux = avformat_alloc_context())) {
+        fprintf(stderr, "Could not allocate demuxer context\n");
+        exit(1);
+    }
+    demux->pb = create_io_context(ctx->abuf, 0);
+    if (!(format = av_find_input_format(ctx->format))) {
+        fprintf(stderr, "Could not find input format for %s\n", ctx->format);
+        exit(1);
+    }
+    if ((ret = av_dict_parse_string(&opts, ctx->demux_opts, "=", ":", 0)) < 0) {
+        fprintf(stderr, "Could not parse demuxer options: %s\n",
+                av_err2str(ret));
+        exit(1);
+    }
+
+    if ((ret = avformat_open_input(&demux, NULL, format, &opts)) < 0) {
+        fprintf(stderr, "Error in avformat_open_input(): %s\n",
+                av_err2str(ret));
+        exit(1);
+    }
+    report_remaining_options("demuxer", opts);
+    report_read(ctx->abuf);
+    printf("demux_open\n");
+
+    if (ctx->do_find_info) {
+        if ((ret = avformat_find_stream_info(demux, NULL)) < 0) {
+            fprintf(stderr, "Error in avformat_find_stream_info(): %s\n",
+                    av_err2str(ret));
+            exit(1);
+        }
+        printf("find_stream_info\n");
+    }
+
+    while (1) {
+        ret = av_read_frame(demux, &packet);
+        report_read(ctx->abuf);
+        if (ret < 0)
+            break;
+        printf("demux_packet %d %d\n", count++, packet.size);
+        av_free_packet(&packet);
+    }
+    printf("demux_eof\n");
+
+    av_free(demux->pb->buffer);
+    av_free(demux->pb);
+    avformat_close_input(&demux);
+    return NULL;
+}
+
+static void open_demuxer(const struct context *ctx, pthread_t *rtid)
+{
+    if (pthread_create(rtid, NULL, demuxer_thread, (void *)ctx) < 0) {
+        perror("pthread_create");
+        exit(1);
+    }
+}
+
+static void wait_demuxer(struct async_buffer *abuf)
+{
+    pthread_cond_signal(&abuf->cond);
+    while (abuf->tail < abuf->head)
+        pthread_cond_wait(&abuf->cond, &abuf->lock);
+    abuf->tail = abuf->head = 0;
+}
+
+int main(int argc, char **argv)
+{
+    struct context ctx = { 0 };
+    char *input_file;
+    AVFormatContext *input = NULL;
+    AVFormatContext *mux;
+    AVPacket packet;
+    AVDictionary *opts = NULL;
+    pthread_t demuxer;
+    int ret;
+    unsigned count = 0;
+    void *dummy;
+
+    while ((ret = getopt(argc, argv, "him:d:")) >= 0) {
+        switch (ret) {
+        case 'i':
+            ctx.do_find_info = 1;
+            break;
+        case 'm':
+            ctx.mux_opts = optarg;
+            break;
+        case 'd':
+            ctx.demux_opts = optarg;
+            break;
+        case 'h':
+            usage(0);
+        default:
+            usage(1);
+        }
+    }
+    argc -= optind;
+    argv += optind;
+    if (argc != 2)
+        usage(1);
+    ctx.format = argv[0];
+    input_file = argv[1];
+
+    av_register_all();
+    if ((ret = avformat_open_input(&input, input_file, NULL, NULL)) < 0 ||
+        (ret = avformat_find_stream_info(input, NULL)) < 0) {
+        fprintf(stderr, "Could not open input file %s: %s\n",
+                input_file, av_err2str(ret));
+        exit(1);
+    }
+
+    if (!(ctx.abuf = av_mallocz(sizeof(*ctx.abuf)))) {
+        fprintf(stderr, "Could not allocate async buffer\n");
+        exit(1);
+    }
+    if (pthread_mutex_init(&ctx.abuf->lock, NULL) < 0) {
+        perror("pthread_mutex_init");
+        exit(1);
+    }
+    if (pthread_cond_init(&ctx.abuf->cond, NULL) < 0) {
+        perror("pthread_cond_init");
+        exit(1);
+    }
+
+    pthread_mutex_lock(&ctx.abuf->lock);
+    open_muxer(&ctx, input, &mux);
+    open_demuxer(&ctx, &demuxer);
+
+    printf("mux_header\n");
+    if ((ret = av_dict_parse_string(&opts, ctx.mux_opts, "=", ":", 0)) < 0) {
+        fprintf(stderr, "Could not parse muxer options: %s\n",
+                av_err2str(ret));
+        exit(1);
+    }
+    if ((ret = avformat_write_header(mux, &opts)) < 0) {
+        fprintf(stderr, "Error in avformat_write_header(): %s\n",
+                av_err2str(ret));
+        exit(1);
+    }
+    report_remaining_options("muxer", opts);
+    wait_demuxer(ctx.abuf);
+
+    while (1) {
+        if ((ret = av_read_frame(input, &packet)) < 0)
+            break;
+        printf("mux_packet %d %d\n", count++, packet.size);
+        av_write_frame(mux, &packet);
+        av_free_packet(&packet);
+        wait_demuxer(ctx.abuf);
+    }
+
+    printf("mux_trailer\n");
+    if ((ret = av_write_trailer(mux)) < 0) {
+        fprintf(stderr, "Error in av_write_trailer(): %s\n", av_err2str(ret));
+        exit(1);
+    }
+
+    ctx.abuf->eof = 1;
+    pthread_mutex_unlock(&ctx.abuf->lock);
+    pthread_cond_signal(&ctx.abuf->cond);
+    if (pthread_join(demuxer, &dummy) < 0) {
+        perror("pthread_join");
+        exit(1);
+    }
+
+    av_free(mux->pb->buffer);
+    av_free(mux->pb);
+    avformat_free_context(mux);
+    avformat_close_input(&input);
+    av_free(ctx.abuf);
+
+    return 0;
+}
-- 
1.7.10.4



More information about the ffmpeg-devel mailing list