[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