From f43d09cd60a17a139b3aeb94434dbbbac1a28dab Mon Sep 17 00:00:00 2001 From: Nicolas George Date: Sat, 9 Feb 2013 20:50:11 +0100 Subject: lavf: add tee pseudo-muxer. --- libavformat/tee.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 libavformat/tee.c (limited to 'libavformat/tee.c') diff --git a/libavformat/tee.c b/libavformat/tee.c new file mode 100644 index 0000000000..f41327bc7e --- /dev/null +++ b/libavformat/tee.c @@ -0,0 +1,278 @@ +/* + * Tee pesudo-muxer + * 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/avutil.h" +#include "libavutil/avstring.h" +#include "libavutil/opt.h" +#include "avformat.h" + +#define MAX_SLAVES 16 + +typedef struct TeeContext { + const AVClass *class; + unsigned nb_slaves; + AVFormatContext *slaves[MAX_SLAVES]; +} TeeContext; + +static const char *const slave_delim = "|"; +static const char *const slave_opt_open = "["; +static const char *const slave_opt_close = "]"; +static const char *const slave_opt_delim = ":]"; /* must have the close too */ + +static const AVClass tee_muxer_class = { + .class_name = "Tee muxer", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, +}; + +static int parse_slave_options(void *log, char *slave, + AVDictionary **options, char **filename) +{ + const char *p; + char *key, *val; + int ret; + + if (!strspn(slave, slave_opt_open)) { + *filename = slave; + return 0; + } + p = slave + 1; + if (strspn(p, slave_opt_close)) { + *filename = (char *)p + 1; + return 0; + } + while (1) { + ret = av_opt_get_key_value(&p, "=", slave_opt_delim, 0, &key, &val); + if (ret < 0) { + av_log(log, AV_LOG_ERROR, "No option found near \"%s\"\n", p); + goto fail; + } + ret = av_dict_set(options, key, val, + AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); + if (ret < 0) + goto fail; + if (strspn(p, slave_opt_close)) + break; + p++; + } + *filename = (char *)p + 1; + return 0; + +fail: + av_dict_free(options); + return ret; +} + +static int open_slave(AVFormatContext *avf, char *slave, AVFormatContext **ravf) +{ + int i, ret; + AVDictionary *options = NULL; + AVDictionaryEntry *entry; + char *filename; + char *format = NULL; + AVFormatContext *avf2 = NULL; + AVStream *st, *st2; + + if ((ret = parse_slave_options(avf, slave, &options, &filename)) < 0) + return ret; + if ((entry = av_dict_get(options, "f", NULL, 0))) { + format = entry->value; + entry->value = NULL; /* prevent it from being freed */ + av_dict_set(&options, "f", NULL, 0); + } + + avformat_alloc_output_context2(&avf2, NULL, format, filename); + if (ret < 0) + goto fail; + av_free(format); + + for (i = 0; i < avf->nb_streams; i++) { + st = avf->streams[i]; + if (!(st2 = avformat_new_stream(avf2, NULL))) { + ret = AVERROR(ENOMEM); + goto fail; + } + st2->id = st->id; + st2->r_frame_rate = st->r_frame_rate; + st2->time_base = st->time_base; + st2->start_time = st->start_time; + st2->duration = st->duration; + st2->nb_frames = st->nb_frames; + st2->disposition = st->disposition; + st2->sample_aspect_ratio = st->sample_aspect_ratio; + st2->avg_frame_rate = st->avg_frame_rate; + av_dict_copy(&st2->metadata, st->metadata, 0); + if ((ret = avcodec_copy_context(st2->codec, st->codec)) < 0) + goto fail; + } + + if (!(avf2->oformat->flags & AVFMT_NOFILE)) { + if ((ret = avio_open(&avf2->pb, filename, AVIO_FLAG_WRITE)) < 0) { + av_log(avf, AV_LOG_ERROR, "Slave '%s': error opening: %s\n", + slave, av_err2str(ret)); + goto fail; + } + } + + if ((ret = avformat_write_header(avf2, &options)) < 0) { + av_log(avf, AV_LOG_ERROR, "Slave '%s': error writing header: %s\n", + slave, av_err2str(ret)); + goto fail; + } + if (options) { + entry = NULL; + while ((entry = av_dict_get(options, "", entry, AV_DICT_IGNORE_SUFFIX))) + av_log(avf2, AV_LOG_ERROR, "Unknown option '%s'\n", entry->key); + ret = AVERROR_OPTION_NOT_FOUND; + goto fail; + } + + *ravf = avf2; + return 0; + +fail: + av_dict_free(&options); + return ret; +} + +static void close_slaves(AVFormatContext *avf) +{ + TeeContext *tee = avf->priv_data; + AVFormatContext *avf2; + unsigned i; + + for (i = 0; i < tee->nb_slaves; i++) { + avf2 = tee->slaves[i]; + avio_close(avf2->pb); + avf2->pb = NULL; + avformat_free_context(avf2); + tee->slaves[i] = NULL; + } +} + +static int tee_write_header(AVFormatContext *avf) +{ + TeeContext *tee = avf->priv_data; + unsigned nb_slaves = 0, i; + const char *filename = avf->filename; + char *slaves[MAX_SLAVES]; + int ret; + + while (*filename) { + if (nb_slaves == MAX_SLAVES) { + av_log(avf, AV_LOG_ERROR, "Maximum %d slave muxers reached.\n", + MAX_SLAVES); + ret = AVERROR_PATCHWELCOME; + goto fail; + } + if (!(slaves[nb_slaves++] = av_get_token(&filename, slave_delim))) { + ret = AVERROR(ENOMEM); + goto fail; + } + if (strspn(filename, slave_delim)) + filename++; + } + + for (i = 0; i < nb_slaves; i++) { + if ((ret = open_slave(avf, slaves[i], &tee->slaves[i])) < 0) + goto fail; + av_freep(&slaves[i]); + } + + tee->nb_slaves = nb_slaves; + return 0; + +fail: + for (i = 0; i < nb_slaves; i++) + av_freep(&slaves[i]); + close_slaves(avf); + return ret; +} + +static int tee_write_trailer(AVFormatContext *avf) +{ + TeeContext *tee = avf->priv_data; + AVFormatContext *avf2; + int ret_all = 0, ret; + unsigned i; + + for (i = 0; i < tee->nb_slaves; i++) { + avf2 = tee->slaves[i]; + if ((ret = av_write_trailer(avf2)) < 0) + if (!ret_all) + ret_all = ret; + if (!(avf2->oformat->flags & AVFMT_NOFILE)) { + if ((ret = avio_close(avf2->pb)) < 0) + if (!ret_all) + ret_all = ret; + avf2->pb = NULL; + } + } + close_slaves(avf); + return ret_all; +} + +static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt) +{ + TeeContext *tee = avf->priv_data; + AVFormatContext *avf2; + AVPacket pkt2; + int ret_all = 0, ret; + unsigned i, s; + AVRational tb, tb2; + + for (i = 0; i < tee->nb_slaves; i++) { + avf2 = tee->slaves[i]; + s = pkt->stream_index; + if (s >= avf2->nb_streams) { + if (!ret_all) + ret_all = AVERROR(EINVAL); + continue; + } + if ((ret = av_copy_packet(&pkt2, pkt)) < 0 || + (ret = av_dup_packet(&pkt2))< 0) + if (!ret_all) { + ret = ret_all; + continue; + } + tb = avf ->streams[s]->time_base; + tb2 = avf2->streams[s]->time_base; + pkt2.pts = av_rescale_q(pkt->pts, tb, tb2); + pkt2.dts = av_rescale_q(pkt->dts, tb, tb2); + pkt2.duration = av_rescale_q(pkt->duration, tb, tb2); + if ((ret = av_interleaved_write_frame(avf2, &pkt2)) < 0) + if (!ret_all) + ret_all = ret; + } + return ret_all; +} + +AVOutputFormat ff_tee_muxer = { + .name = "tee", + .long_name = NULL_IF_CONFIG_SMALL("Multiple muxer tee"), + .priv_data_size = sizeof(TeeContext), + .write_header = tee_write_header, + .write_trailer = tee_write_trailer, + .write_packet = tee_write_packet, + .priv_class = &tee_muxer_class, + .flags = AVFMT_NOFILE, +}; -- cgit v1.2.3