From 32aedebdc59d5b34ab7a9137855dcc602267e00f Mon Sep 17 00:00:00 2001 From: Nicolas George Date: Wed, 22 Aug 2012 18:28:37 +0200 Subject: [PATCH] lavf: add a concat demuxer. --- Changelog | 1 + doc/demuxers.texi | 31 ++++++ libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/concatdec.c | 221 +++++++++++++++++++++++++++++++++++++++ libavformat/version.h | 2 +- 6 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 libavformat/concatdec.c diff --git a/Changelog b/Changelog index fc23bd0bb0..2691975f1f 100644 --- a/Changelog +++ b/Changelog @@ -36,6 +36,7 @@ version : - subtitles filter - IRCAM muxer/demuxer - Paris Audio File demuxer +- Virtual concatenation demuxer version 1.0: diff --git a/doc/demuxers.texi b/doc/demuxers.texi index aea4c54c0b..126dd1d18d 100644 --- a/doc/demuxers.texi +++ b/doc/demuxers.texi @@ -184,4 +184,35 @@ the script is directly played, the actual times will match the absolute timestamps up to the sound controller's clock accuracy, but if the user somehow pauses the playback or seeks, all times will be shifted accordingly. +@section concat + +Virtual concatenation script demuxer. + +This demuxer reads a list of files and other directives from a text file and +demuxes them one after the other, as if all their packet had been muxed +together. + +The timestamps in the files are adjusted so that the first file starts at 0 +and each next file starts where the previous one finishes. Note that it is +done globally and may cause gaps if all streams do not have exactly the same +length. + +All files must have the same streams (same codecs, same time base, etc.). + +This script format can currently not be probed, it must be specified explicitly. + +@subsection Syntax + +The script is a text file in extended-ASCII, with one directive per line. +Empty lines, leading spaces and lines starting with '#' are ignored. The +following directive is recognized: + +@table @option + +@item @code{file @var{path}} +Path to a file to read; special characters and spaces must be escaped with +backslash or single quotes. + +@end table + @c man end INPUT DEVICES diff --git a/libavformat/Makefile b/libavformat/Makefile index f8d99955e1..d3d03b448c 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -96,6 +96,7 @@ OBJS-$(CONFIG_CAVSVIDEO_DEMUXER) += cavsvideodec.o rawdec.o OBJS-$(CONFIG_CAVSVIDEO_MUXER) += rawenc.o OBJS-$(CONFIG_CDG_DEMUXER) += cdg.o OBJS-$(CONFIG_CDXL_DEMUXER) += cdxl.o +OBJS-$(CONFIG_CONCAT_DEMUXER) += concatdec.o OBJS-$(CONFIG_CRC_MUXER) += crcenc.o OBJS-$(CONFIG_DAUD_DEMUXER) += daud.o OBJS-$(CONFIG_DAUD_MUXER) += daud.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 7c72fe8828..81289c3076 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -85,6 +85,7 @@ void av_register_all(void) REGISTER_MUXDEMUX (CAVSVIDEO, cavsvideo); REGISTER_DEMUXER (CDG, cdg); REGISTER_DEMUXER (CDXL, cdxl); + REGISTER_DEMUXER (CONCAT, concat); REGISTER_MUXER (CRC, crc); REGISTER_MUXDEMUX (DAUD, daud); REGISTER_DEMUXER (DFA, dfa); diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c new file mode 100644 index 0000000000..29233e2c8c --- /dev/null +++ b/libavformat/concatdec.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2012 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 + */ + +#include "libavutil/avstring.h" +#include "avformat.h" +#include "internal.h" + +typedef struct { + char *url; + int64_t start_time; + int64_t duration; +} ConcatFile; + +typedef struct { + ConcatFile *files; + ConcatFile *cur_file; + unsigned nb_files; + AVFormatContext *avf; +} ConcatContext; + +static int concat_probe(AVProbeData *probe) +{ + return 0; +} + +static char *get_keyword(uint8_t **cursor) +{ + char *ret = *cursor += strspn(*cursor, SPACE_CHARS); + *cursor += strcspn(*cursor, SPACE_CHARS); + if (**cursor) { + *((*cursor)++) = 0; + *cursor += strspn(*cursor, SPACE_CHARS); + } + return ret; +} + +#define FAIL(retcode) do { ret = (retcode); goto fail; } while(0) + +static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile, + unsigned *nb_files_alloc) +{ + ConcatContext *cat = avf->priv_data; + ConcatFile *file; + char *url; + size_t url_len; + + url_len = strlen(avf->filename) + strlen(filename) + 16; + if (!(url = av_malloc(url_len))) + return AVERROR(ENOMEM); + ff_make_absolute_url(url, url_len, avf->filename, filename); + av_free(filename); + + if (cat->nb_files >= *nb_files_alloc) { + unsigned n = FFMAX(*nb_files_alloc * 2, 16); + if (n <= cat->nb_files || + !(cat->files = av_realloc_f(cat->files, n, sizeof(*cat->files)))) + return AVERROR(ENOMEM); + *nb_files_alloc = n; + } + + file = &cat->files[cat->nb_files++]; + memset(file, 0, sizeof(*file)); + *rfile = file; + + file->url = url; + file->start_time = AV_NOPTS_VALUE; + file->duration = AV_NOPTS_VALUE; + + return 0; +} + +static int open_file(AVFormatContext *avf, unsigned fileno) +{ + ConcatContext *cat = avf->priv_data; + ConcatFile *file = &cat->files[fileno]; + int ret; + + if ((ret = avformat_open_input(&cat->avf, file->url, NULL, NULL)) < 0 || + (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) { + av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url); + return ret; + } + cat->cur_file = file; + if (file->start_time == AV_NOPTS_VALUE) + file->start_time = !fileno ? 0 : + cat->files[fileno - 1].start_time + + cat->files[fileno - 1].duration; + return 0; +} + +static int concat_read_close(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + unsigned i; + + if (cat->avf) + avformat_close_input(&cat->avf); + for (i = 0; i < cat->nb_files; i++) + av_freep(&cat->files[i].url); + av_freep(&cat->files); + return 0; +} + +static int concat_read_header(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + uint8_t buf[4096]; + uint8_t *cursor, *keyword; + int ret, line = 0, i; + unsigned nb_files_alloc = 0; + ConcatFile *file = NULL; + AVStream *st, *source_st; + + while (1) { + if ((ret = ff_get_line(avf->pb, buf, sizeof(buf))) <= 0) + break; + line++; + cursor = buf; + keyword = get_keyword(&cursor); + if (!*keyword || *keyword == '#') + continue; + + if (!strcmp(keyword, "file")) { + char *filename = av_get_token((const char **)&cursor, SPACE_CHARS); + if (!filename) { + av_log(avf, AV_LOG_ERROR, "Line %d: filename required\n", line); + FAIL(AVERROR_INVALIDDATA); + } + if ((ret = add_file(avf, filename, &file, &nb_files_alloc)) < 0) + FAIL(ret); + } else { + av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n", + line, keyword); + FAIL(AVERROR_INVALIDDATA); + } + } + if (ret < 0) + FAIL(ret); + + if ((ret = open_file(avf, 0)) < 0) + FAIL(ret); + for (i = 0; i < cat->avf->nb_streams; i++) { + if (!(st = avformat_new_stream(avf, NULL))) + FAIL(AVERROR(ENOMEM)); + source_st = cat->avf->streams[i]; + if ((ret = avcodec_copy_context(st->codec, source_st->codec)) < 0) + FAIL(ret); + st->r_frame_rate = source_st->r_frame_rate; + st->avg_frame_rate = source_st->avg_frame_rate; + st->time_base = source_st->time_base; + st->sample_aspect_ratio = source_st->sample_aspect_ratio; + } + + return 0; + +fail: + concat_read_close(avf); + return ret; +} + +static int open_next_file(AVFormatContext *avf) +{ + ConcatContext *cat = avf->priv_data; + unsigned fileno = cat->cur_file - cat->files; + + if (cat->cur_file->duration == AV_NOPTS_VALUE) + cat->cur_file->duration = cat->avf->duration; + + if (++fileno >= cat->nb_files) + return AVERROR_EOF; + avformat_close_input(&cat->avf); + return open_file(avf, fileno); +} + +static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt) +{ + ConcatContext *cat = avf->priv_data; + int ret; + int64_t delta; + + while (1) { + if ((ret = av_read_frame(cat->avf, pkt)) != AVERROR_EOF || + (ret = open_next_file(avf)) < 0) + break; + } + delta = av_rescale_q(cat->cur_file->start_time - cat->avf->start_time, + AV_TIME_BASE_Q, + cat->avf->streams[pkt->stream_index]->time_base); + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += delta; + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += delta; + return ret; +} + +AVInputFormat ff_concat_demuxer = { + .name = "concat", + .long_name = NULL_IF_CONFIG_SMALL("Virtual concatenation script"), + .priv_data_size = sizeof(ConcatContext), + .read_probe = concat_probe, + .read_header = concat_read_header, + .read_packet = concat_read_packet, + .read_close = concat_read_close, +}; diff --git a/libavformat/version.h b/libavformat/version.h index 4ffaa98409..1572335c9f 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -30,7 +30,7 @@ #include "libavutil/avutil.h" #define LIBAVFORMAT_VERSION_MAJOR 54 -#define LIBAVFORMAT_VERSION_MINOR 46 +#define LIBAVFORMAT_VERSION_MINOR 47 #define LIBAVFORMAT_VERSION_MICRO 100 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \