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 +@anchor{fftextdata} +@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 +@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. + +@subsection Options +@table @option +@item codec_name +Set the codec name for the packets data. +@end table + +@subsection Examples + +@itemize +@item +Inject timed_id3 packed data stored into the data.fftd file into the +output file. +@example +ffmpeg -i input.mp4 -codec_name timed_id3 -f fftextdata -i data.fftd -y -map 0 -map 1 -c copy output.ts +@end example +@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 +@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 +@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. + +@subsection Examples + +@itemize +@item +Store a data stream to an output file: +@example +ffmpeg -i INPUT -codec copy -map 0 -an -vn data.fftd +@end example +@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel