[FFmpeg-devel] [PATCH] lavf: add textdata virtual demuxer and demuxer

Stefano Sabatini stefasab at gmail.com
Thu May 19 18:45:22 CEST 2016


This format is useful to inject custom user data into streams.
---
 doc/demuxers.texi           |  40 +++++++++
 doc/muxers.texi             |  31 +++++++
 libavformat/Makefile        |   2 +
 libavformat/allformats.c    |   1 +
 libavformat/fftextdatadec.c | 212 ++++++++++++++++++++++++++++++++++++++++++++
 libavformat/fftextdataenc.c | 103 +++++++++++++++++++++
 6 files changed, 389 insertions(+)
 create mode 100644 libavformat/fftextdatadec.c
 create mode 100644 libavformat/fftextdataenc.c

diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index e34f8b3..9fc58eb 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -254,6 +254,46 @@ This demuxer is used to demux FLV files and RTMP network streams.
 Allocate the streams according to the onMetaData array content.
 @end table
 
+ at anchor{fftextdata}
+ at section fftextdata, fftd
+
+FFmpeg text data demuxer.
+
+This special demuxer allows to read serialized data base64-encoded and
+remux it. It is especially useful for injecting opaque data streams.
+
+The fftextdata bytestream consists of a sequence of packets. Each
+packet starts with a timestamps expressed in a format recognized by
+FFmpeg (see
+ at ref{time duration syntax,,the Time duration section in the ffmpeg-utils(1) manual,ffmpeg-utils})
+followed by a sequence of spaces and the base64 encoded data for the
+packet, terminated by ";". The data representation may contain
+interleaved space characters (a space, a tab, or a newline) which are
+ignored.
+
+At the moment a single stream can be represented by an fftextdata
+bytestream.
+
+If an input filename is "fftextdata" or "fftd" then the file format is
+recognized as fftextdata.
+
+ at subsection Options
+ at table @option
+ at item codec_name
+Set the codec name for the packets data.
+ at end table
+
+ at subsection Examples
+
+ at itemize
+ at item
+Inject timed_id3 packed data stored into the data.fftd file into the
+output file.
+ at example
+ffmpeg -i input.mp4 -codec_name timed_id3 -f fftextdata -i data.fftd -y -map 0 -map 1 -c copy output.ts
+ at end example
+ at end itemize
+
 @section libgme
 
 The Game Music Emu library is a collection of video game music file emulators.
diff --git a/doc/muxers.texi b/doc/muxers.texi
index c62d4b5..df5ec08 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -129,6 +129,37 @@ and the input video converted to MPEG-2 video, use the command:
 ffmpeg -i INPUT -c:a pcm_u8 -c:v mpeg2video -f crc -
 @end example
 
+ at section fftextdata, fftd
+
+FFmpeg text data muxer.
+
+The fftextdata bytestream consists of a sequence of packets. Each
+packet starts with a timestamps expressed in a format recognized by
+FFmpeg (see
+ at ref{time duration syntax,,the Time duration section in the ffmpeg-utils(1) manual,ffmpeg-utils})
+followed by a sequence of spaces and the base64 encoded data for the
+packet, terminated by ";". The data representation may contain
+interleaved space characters (a space, a tab, or a newline) which are
+ignored.
+
+At the moment only a single stream can be represented by an fftextdata
+bytestream.
+
+This muxer can be used to reinject the stream (e.g. a data stream) in
+a different output, or to provide serialized data of the encoded data.
+
+The output can then be read using the fftextdata demuxer.
+
+ at subsection Examples
+
+ at itemize
+ at item
+Store a data stream to an output file:
+ at example
+ffmpeg -i INPUT -codec copy -map 0 -an -vn data.fftd
+ at end example
+ at end itemize
+
 @anchor{framecrc}
 @section framecrc
 
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 742aff5..4effccd 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -162,6 +162,8 @@ OBJS-$(CONFIG_FFM_DEMUXER)               += ffmdec.o
 OBJS-$(CONFIG_FFM_MUXER)                 += ffmenc.o
 OBJS-$(CONFIG_FFMETADATA_DEMUXER)        += ffmetadec.o
 OBJS-$(CONFIG_FFMETADATA_MUXER)          += ffmetaenc.o
+OBJS-$(CONFIG_FFTEXTDATA_DEMUXER)        += fftextdatadec.o
+OBJS-$(CONFIG_FFTEXTDATA_MUXER)          += fftextdataenc.o
 OBJS-$(CONFIG_FILMSTRIP_DEMUXER)         += filmstripdec.o
 OBJS-$(CONFIG_FILMSTRIP_MUXER)           += filmstripenc.o
 OBJS-$(CONFIG_FLAC_DEMUXER)              += flacdec.o rawdec.o \
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index e6ee8d6..7657f94 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -124,6 +124,7 @@ void av_register_all(void)
     REGISTER_MUXER   (F4V,              f4v);
     REGISTER_MUXDEMUX(FFM,              ffm);
     REGISTER_MUXDEMUX(FFMETADATA,       ffmetadata);
+    REGISTER_MUXDEMUX(FFTEXTDATA,       fftextdata);
     REGISTER_MUXDEMUX(FILMSTRIP,        filmstrip);
     REGISTER_MUXDEMUX(FLAC,             flac);
     REGISTER_DEMUXER (FLIC,             flic);
diff --git a/libavformat/fftextdatadec.c b/libavformat/fftextdatadec.c
new file mode 100644
index 0000000..9516559
--- /dev/null
+++ b/libavformat/fftextdatadec.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2016 Stefano Sabatini
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * timestamped data virtual demuxer
+ */
+
+#include "libavutil/base64.h"
+#include "libavutil/bprint.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
+#include "avformat.h"
+#include "internal.h"
+
+typedef struct {
+    const AVClass *class;  /**< Class for private options. */
+    int nb_packets;
+    AVBPrint bp;
+    const char *codec_name;
+} FFTextdataContext;
+
+av_cold static int fftextdata_read_close(AVFormatContext *avctx)
+{
+    FFTextdataContext *td = avctx->priv_data;
+
+    av_bprint_finalize(&td->bp, NULL);
+    return 0;
+}
+
+av_cold static int fftextdata_read_header(AVFormatContext *s)
+{
+    FFTextdataContext *td = s->priv_data;
+    AVStream *st;
+    const AVCodecDescriptor *cd;
+
+    st = avformat_new_stream(s, NULL);
+    if (!st)
+        return AVERROR(ENOMEM);
+
+    cd = avcodec_descriptor_get_by_name(td->codec_name);
+    if (!cd) {
+        av_log(s, AV_LOG_ERROR, "Impossible to find a codec with name '%s'\n",
+               td->codec_name);
+        return AVERROR(EINVAL);
+    }
+
+    st->codecpar->codec_type = cd->type;
+    st->codecpar->codec_id = cd->id;
+    avpriv_set_pts_info(st, 64, 1, 1000000);
+
+    av_bprint_init(&(td->bp), 0, 1);
+    td->nb_packets = 0;
+
+    return 0;
+}
+
+static inline int is_space(char c)
+{
+    return c == ' '  || c == '\t' || c == '\r' || c == '\n';
+}
+
+static int read_word(AVIOContext *avio, AVBPrint *bp)
+{
+    int c;
+
+    av_bprint_clear(bp);
+
+    /* skip spaces */
+    do {
+        c = avio_r8(avio);
+        if (!c)
+            goto end;
+    } while (is_space(c));
+
+    /* read word */
+    av_bprint_chars(bp, c, 1);
+    do {
+        c = avio_r8(avio);
+        if (!c)
+            goto end;
+        if (is_space(c)) {
+            avio_skip(avio, -1);
+            goto end;
+        }
+        av_bprint_chars(bp, c, 1);
+    } while (1);
+
+end:
+    return bp->len;
+}
+
+static int read_data(AVIOContext *avio, AVBPrint *bp)
+{
+    int c;
+
+    av_bprint_clear(bp);
+
+    /* skip spaces */
+    do {
+        c = avio_r8(avio);
+        if (!c)
+            goto end;
+    } while (is_space(c));
+
+    /* read data chunk */
+    av_bprint_chars(bp, c, 1);
+    do {
+        c = avio_r8(avio);
+        if (!c || c == ';')
+            goto end;
+        if (is_space(c)) {
+            continue;
+        }
+        av_bprint_chars(bp, c, 1);
+    } while (1);
+
+end:
+    return bp->len;
+}
+
+static int fftextdata_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    FFTextdataContext *td = s->priv_data;
+    AVIOContext *avio = s->pb;
+    int ret;
+    AVBPrint *bp = &(td->bp);
+
+    pkt->pos = avio_tell(avio);
+
+    /* read PTS  */
+    ret = read_word(avio, bp);
+    if (ret == 0)
+        return AVERROR_EOF;
+
+    ret = av_parse_time(&pkt->pts, bp->str, 1);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Invalid time specification '%s' for data packet #%d\n",
+               bp->str, td->nb_packets);
+        return ret;
+    }
+
+    ret = read_data(avio, bp);
+    if (ret == 0) {
+        av_log(s, AV_LOG_WARNING, "Incomplete packet #%d with no data at the end of the data stream\n",
+               td->nb_packets);
+        return AVERROR_EOF;
+    }
+
+    pkt->size = AV_BASE64_DECODE_SIZE(ret);
+    pkt->data = av_malloc(pkt->size);
+    if (ret < 0)
+        return ret;
+
+    ret = av_base64_decode(pkt->data, bp->str, pkt->size);
+    if (ret < 0) {
+        av_freep(&pkt->data);
+        return ret;
+    }
+
+    pkt->size = ret;
+    pkt->flags |= AV_PKT_FLAG_KEY;
+    td->nb_packets++;
+
+    return ret;
+}
+
+#define OFFSET(x) offsetof(FFTextdataContext, x)
+
+#define D AV_OPT_FLAG_DECODING_PARAM
+
+#define OFFSET(x) offsetof(FFTextdataContext, x)
+
+static const AVOption options[] = {
+    { "codec_name",  "set output codec name", OFFSET(codec_name), AV_OPT_TYPE_STRING, {.str = "bin_data"}, CHAR_MIN, CHAR_MAX, D },
+    { NULL },
+};
+
+static const AVClass fftextdata_class = {
+    .class_name = "fftexdata demuxer",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+AVInputFormat ff_fftextdata_demuxer = {
+    .name           = "fftextdata",
+    .long_name      = NULL_IF_CONFIG_SMALL("Timestamped data virtual demuxer"),
+    .extensions     = "fftextdata,fftd",
+    .priv_data_size = sizeof(FFTextdataContext),
+    .read_header    = fftextdata_read_header,
+    .read_packet    = fftextdata_read_packet,
+    .read_close     = fftextdata_read_close,
+    .priv_class     = &fftextdata_class,
+};
diff --git a/libavformat/fftextdataenc.c b/libavformat/fftextdataenc.c
new file mode 100644
index 0000000..6029ab0
--- /dev/null
+++ b/libavformat/fftextdataenc.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2016 Stefano Sabatini
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * timestamped data virtual muxer
+ */
+
+#include "avformat.h"
+#include "libavutil/base64.h"
+
+typedef struct {
+    uint8_t *buf;
+    size_t   buf_size;
+} FFTextdataContext;
+
+static int fftextdata_write_header(AVFormatContext *s)
+{
+    FFTextdataContext *td = s->priv_data;
+
+    td->buf = NULL;
+    td->buf_size = 0;
+
+    return 0;
+}
+
+static int fftextdata_write_trailer(AVFormatContext *s)
+{
+    FFTextdataContext *td = s->priv_data;
+
+    av_freep(&td->buf);
+    td->buf_size = 0;
+
+    return 0;
+}
+
+static int fftextdata_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    FFTextdataContext *td = s->priv_data;
+    char ts[32];
+    size_t encoded_data_size;
+    AVStream *st = s->streams[pkt->stream_index];
+    int64_t pts = pkt->pts;
+    double secs;
+    int hours, mins;
+
+    if (st->start_time != AV_NOPTS_VALUE)
+        pts += st->start_time;
+
+    secs = (double)pkt->pts * av_q2d(st->time_base);
+    mins  = (int)secs / 60;
+    secs  = secs - mins * 60;
+    hours = mins / 60;
+    mins %= 60;
+    snprintf(ts, sizeof(ts), "%d:%02d:%09.6f", hours, mins, secs);
+    avio_put_str(s->pb, ts);
+    avio_skip(s->pb, -1);
+    avio_w8(s->pb, '\n');
+
+    encoded_data_size = AV_BASE64_SIZE(pkt->size);
+    if (encoded_data_size > td->buf_size) {
+        td->buf = av_realloc_f(td->buf, encoded_data_size, 1);
+        if (!td->buf)
+            return AVERROR(ENOMEM);
+        td->buf_size = encoded_data_size;
+    }
+
+    av_base64_encode(td->buf, td->buf_size, pkt->data, pkt->size);
+    avio_put_str(s->pb, td->buf);
+    avio_skip(s->pb, -1);
+
+    avio_put_str(s->pb, "\n;\n");
+    avio_skip(s->pb, -1);
+
+    return 0;
+}
+
+AVOutputFormat ff_fftextdata_muxer = {
+    .name          = "fftextdata",
+    .long_name     = NULL_IF_CONFIG_SMALL("Timestamped data virtual muxer"),
+    .extensions    = "fftextdata,fftd",
+    .priv_data_size = sizeof(FFTextdataContext),
+    .write_header  = fftextdata_write_header,
+    .write_packet  = fftextdata_write_packet,
+    .write_trailer = fftextdata_write_trailer,
+};
-- 
1.9.1



More information about the ffmpeg-devel mailing list