... | ... |
@@ -749,4 +749,39 @@ situations, giving a small seek granularity at the cost of additional container |
749 | 749 |
overhead. |
750 | 750 |
@end table |
751 | 751 |
|
752 |
+@section tee |
|
753 |
+ |
|
754 |
+The tee muxer can be used to write the same data to several files or any |
|
755 |
+other kind of muxer. It can be used, for example, to both stream a video to |
|
756 |
+the network and save it to disk at the same time. |
|
757 |
+ |
|
758 |
+It is different from specifying several outputs to the @command{ffmpeg} |
|
759 |
+command-line tool because the audio and video data will be encoded only once |
|
760 |
+with the tee muxer; encoding can be a very expensive process. It is not |
|
761 |
+useful when using the libavformat API directly because it is then possible |
|
762 |
+to feed the same packets to several muxers directly. |
|
763 |
+ |
|
764 |
+The slave outputs are specified in the file name given to the muxer, |
|
765 |
+separated by '|'. If any of the slave name contains the '|' separator, |
|
766 |
+leading or trailing spaces or any special character, it must be |
|
767 |
+@ref{quoting_and_escaping, escaped}. |
|
768 |
+ |
|
769 |
+Options can be specified for each slave by prepending them as a list of |
|
770 |
+@var{key}=@var{value} pairs separated by ':', between square brackets. If |
|
771 |
+the options values contain a special character or the ':' separator, they |
|
772 |
+must be @ref{quoting_and_escaping, escaped}; note that this is a second |
|
773 |
+level escaping. |
|
774 |
+ |
|
775 |
+Example: encode something and both archive it in a WebM file and stream it |
|
776 |
+as MPEG-TS over UDP: |
|
777 |
+ |
|
778 |
+@example |
|
779 |
+ffmpeg -i ... -c:v libx264 -c:a mp2 -f tee |
|
780 |
+ "archive-20121107.mkv|[f=mpegts]udp://10.0.1.255:1234/" |
|
781 |
+@end example |
|
782 |
+ |
|
783 |
+Note: some codecs may need different options depending on the output format; |
|
784 |
+the auto-detection of this can not work with the tee muxer. The main example |
|
785 |
+is the @option{global_header} flag. |
|
786 |
+ |
|
752 | 787 |
@c man end MUXERS |
... | ... |
@@ -358,6 +358,7 @@ OBJS-$(CONFIG_SWF_DEMUXER) += swfdec.o swf.o |
358 | 358 |
OBJS-$(CONFIG_SWF_MUXER) += swfenc.o swf.o |
359 | 359 |
OBJS-$(CONFIG_TAK_DEMUXER) += takdec.o apetag.o img2.o rawdec.o |
360 | 360 |
OBJS-$(CONFIG_TEDCAPTIONS_DEMUXER) += tedcaptionsdec.o |
361 |
+OBJS-$(CONFIG_TEE_MUXER) += tee.o |
|
361 | 362 |
OBJS-$(CONFIG_THP_DEMUXER) += thp.o |
362 | 363 |
OBJS-$(CONFIG_TIERTEXSEQ_DEMUXER) += tiertexseq.o |
363 | 364 |
OBJS-$(CONFIG_MKVTIMESTAMP_V2_MUXER) += mkvtimestamp_v2.o |
... | ... |
@@ -263,6 +263,7 @@ void av_register_all(void) |
263 | 263 |
REGISTER_DEMUXER (SUBVIEWER, subviewer); |
264 | 264 |
REGISTER_MUXDEMUX(SWF, swf); |
265 | 265 |
REGISTER_DEMUXER (TAK, tak); |
266 |
+ REGISTER_MUXER (TEE, tee); |
|
266 | 267 |
REGISTER_DEMUXER (TEDCAPTIONS, tedcaptions); |
267 | 268 |
REGISTER_MUXER (TG2, tg2); |
268 | 269 |
REGISTER_MUXER (TGP, tgp); |
269 | 270 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,278 @@ |
0 |
+/* |
|
1 |
+ * Tee pesudo-muxer |
|
2 |
+ * Copyright (c) 2012 Nicolas George |
|
3 |
+ * |
|
4 |
+ * This file is part of FFmpeg. |
|
5 |
+ * |
|
6 |
+ * FFmpeg is free software; you can redistribute it and/or |
|
7 |
+ * modify it under the terms of the GNU Lesser General Public License |
|
8 |
+ * as published by the Free Software Foundation; either |
|
9 |
+ * version 2.1 of the License, or (at your option) any later version. |
|
10 |
+ * |
|
11 |
+ * FFmpeg is distributed in the hope that it will be useful, |
|
12 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
+ * GNU Lesser General Public License for more details. |
|
15 |
+ * |
|
16 |
+ * You should have received a copy of the GNU Lesser General Public License |
|
17 |
+ * along with FFmpeg; if not, write to the Free Software * Foundation, Inc., |
|
18 |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
19 |
+ */ |
|
20 |
+ |
|
21 |
+ |
|
22 |
+#include "libavutil/avutil.h" |
|
23 |
+#include "libavutil/avstring.h" |
|
24 |
+#include "libavutil/opt.h" |
|
25 |
+#include "avformat.h" |
|
26 |
+ |
|
27 |
+#define MAX_SLAVES 16 |
|
28 |
+ |
|
29 |
+typedef struct TeeContext { |
|
30 |
+ const AVClass *class; |
|
31 |
+ unsigned nb_slaves; |
|
32 |
+ AVFormatContext *slaves[MAX_SLAVES]; |
|
33 |
+} TeeContext; |
|
34 |
+ |
|
35 |
+static const char *const slave_delim = "|"; |
|
36 |
+static const char *const slave_opt_open = "["; |
|
37 |
+static const char *const slave_opt_close = "]"; |
|
38 |
+static const char *const slave_opt_delim = ":]"; /* must have the close too */ |
|
39 |
+ |
|
40 |
+static const AVClass tee_muxer_class = { |
|
41 |
+ .class_name = "Tee muxer", |
|
42 |
+ .item_name = av_default_item_name, |
|
43 |
+ .version = LIBAVUTIL_VERSION_INT, |
|
44 |
+}; |
|
45 |
+ |
|
46 |
+static int parse_slave_options(void *log, char *slave, |
|
47 |
+ AVDictionary **options, char **filename) |
|
48 |
+{ |
|
49 |
+ const char *p; |
|
50 |
+ char *key, *val; |
|
51 |
+ int ret; |
|
52 |
+ |
|
53 |
+ if (!strspn(slave, slave_opt_open)) { |
|
54 |
+ *filename = slave; |
|
55 |
+ return 0; |
|
56 |
+ } |
|
57 |
+ p = slave + 1; |
|
58 |
+ if (strspn(p, slave_opt_close)) { |
|
59 |
+ *filename = (char *)p + 1; |
|
60 |
+ return 0; |
|
61 |
+ } |
|
62 |
+ while (1) { |
|
63 |
+ ret = av_opt_get_key_value(&p, "=", slave_opt_delim, 0, &key, &val); |
|
64 |
+ if (ret < 0) { |
|
65 |
+ av_log(log, AV_LOG_ERROR, "No option found near \"%s\"\n", p); |
|
66 |
+ goto fail; |
|
67 |
+ } |
|
68 |
+ ret = av_dict_set(options, key, val, |
|
69 |
+ AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); |
|
70 |
+ if (ret < 0) |
|
71 |
+ goto fail; |
|
72 |
+ if (strspn(p, slave_opt_close)) |
|
73 |
+ break; |
|
74 |
+ p++; |
|
75 |
+ } |
|
76 |
+ *filename = (char *)p + 1; |
|
77 |
+ return 0; |
|
78 |
+ |
|
79 |
+fail: |
|
80 |
+ av_dict_free(options); |
|
81 |
+ return ret; |
|
82 |
+} |
|
83 |
+ |
|
84 |
+static int open_slave(AVFormatContext *avf, char *slave, AVFormatContext **ravf) |
|
85 |
+{ |
|
86 |
+ int i, ret; |
|
87 |
+ AVDictionary *options = NULL; |
|
88 |
+ AVDictionaryEntry *entry; |
|
89 |
+ char *filename; |
|
90 |
+ char *format = NULL; |
|
91 |
+ AVFormatContext *avf2 = NULL; |
|
92 |
+ AVStream *st, *st2; |
|
93 |
+ |
|
94 |
+ if ((ret = parse_slave_options(avf, slave, &options, &filename)) < 0) |
|
95 |
+ return ret; |
|
96 |
+ if ((entry = av_dict_get(options, "f", NULL, 0))) { |
|
97 |
+ format = entry->value; |
|
98 |
+ entry->value = NULL; /* prevent it from being freed */ |
|
99 |
+ av_dict_set(&options, "f", NULL, 0); |
|
100 |
+ } |
|
101 |
+ |
|
102 |
+ avformat_alloc_output_context2(&avf2, NULL, format, filename); |
|
103 |
+ if (ret < 0) |
|
104 |
+ goto fail; |
|
105 |
+ av_free(format); |
|
106 |
+ |
|
107 |
+ for (i = 0; i < avf->nb_streams; i++) { |
|
108 |
+ st = avf->streams[i]; |
|
109 |
+ if (!(st2 = avformat_new_stream(avf2, NULL))) { |
|
110 |
+ ret = AVERROR(ENOMEM); |
|
111 |
+ goto fail; |
|
112 |
+ } |
|
113 |
+ st2->id = st->id; |
|
114 |
+ st2->r_frame_rate = st->r_frame_rate; |
|
115 |
+ st2->time_base = st->time_base; |
|
116 |
+ st2->start_time = st->start_time; |
|
117 |
+ st2->duration = st->duration; |
|
118 |
+ st2->nb_frames = st->nb_frames; |
|
119 |
+ st2->disposition = st->disposition; |
|
120 |
+ st2->sample_aspect_ratio = st->sample_aspect_ratio; |
|
121 |
+ st2->avg_frame_rate = st->avg_frame_rate; |
|
122 |
+ av_dict_copy(&st2->metadata, st->metadata, 0); |
|
123 |
+ if ((ret = avcodec_copy_context(st2->codec, st->codec)) < 0) |
|
124 |
+ goto fail; |
|
125 |
+ } |
|
126 |
+ |
|
127 |
+ if (!(avf2->oformat->flags & AVFMT_NOFILE)) { |
|
128 |
+ if ((ret = avio_open(&avf2->pb, filename, AVIO_FLAG_WRITE)) < 0) { |
|
129 |
+ av_log(avf, AV_LOG_ERROR, "Slave '%s': error opening: %s\n", |
|
130 |
+ slave, av_err2str(ret)); |
|
131 |
+ goto fail; |
|
132 |
+ } |
|
133 |
+ } |
|
134 |
+ |
|
135 |
+ if ((ret = avformat_write_header(avf2, &options)) < 0) { |
|
136 |
+ av_log(avf, AV_LOG_ERROR, "Slave '%s': error writing header: %s\n", |
|
137 |
+ slave, av_err2str(ret)); |
|
138 |
+ goto fail; |
|
139 |
+ } |
|
140 |
+ if (options) { |
|
141 |
+ entry = NULL; |
|
142 |
+ while ((entry = av_dict_get(options, "", entry, AV_DICT_IGNORE_SUFFIX))) |
|
143 |
+ av_log(avf2, AV_LOG_ERROR, "Unknown option '%s'\n", entry->key); |
|
144 |
+ ret = AVERROR_OPTION_NOT_FOUND; |
|
145 |
+ goto fail; |
|
146 |
+ } |
|
147 |
+ |
|
148 |
+ *ravf = avf2; |
|
149 |
+ return 0; |
|
150 |
+ |
|
151 |
+fail: |
|
152 |
+ av_dict_free(&options); |
|
153 |
+ return ret; |
|
154 |
+} |
|
155 |
+ |
|
156 |
+static void close_slaves(AVFormatContext *avf) |
|
157 |
+{ |
|
158 |
+ TeeContext *tee = avf->priv_data; |
|
159 |
+ AVFormatContext *avf2; |
|
160 |
+ unsigned i; |
|
161 |
+ |
|
162 |
+ for (i = 0; i < tee->nb_slaves; i++) { |
|
163 |
+ avf2 = tee->slaves[i]; |
|
164 |
+ avio_close(avf2->pb); |
|
165 |
+ avf2->pb = NULL; |
|
166 |
+ avformat_free_context(avf2); |
|
167 |
+ tee->slaves[i] = NULL; |
|
168 |
+ } |
|
169 |
+} |
|
170 |
+ |
|
171 |
+static int tee_write_header(AVFormatContext *avf) |
|
172 |
+{ |
|
173 |
+ TeeContext *tee = avf->priv_data; |
|
174 |
+ unsigned nb_slaves = 0, i; |
|
175 |
+ const char *filename = avf->filename; |
|
176 |
+ char *slaves[MAX_SLAVES]; |
|
177 |
+ int ret; |
|
178 |
+ |
|
179 |
+ while (*filename) { |
|
180 |
+ if (nb_slaves == MAX_SLAVES) { |
|
181 |
+ av_log(avf, AV_LOG_ERROR, "Maximum %d slave muxers reached.\n", |
|
182 |
+ MAX_SLAVES); |
|
183 |
+ ret = AVERROR_PATCHWELCOME; |
|
184 |
+ goto fail; |
|
185 |
+ } |
|
186 |
+ if (!(slaves[nb_slaves++] = av_get_token(&filename, slave_delim))) { |
|
187 |
+ ret = AVERROR(ENOMEM); |
|
188 |
+ goto fail; |
|
189 |
+ } |
|
190 |
+ if (strspn(filename, slave_delim)) |
|
191 |
+ filename++; |
|
192 |
+ } |
|
193 |
+ |
|
194 |
+ for (i = 0; i < nb_slaves; i++) { |
|
195 |
+ if ((ret = open_slave(avf, slaves[i], &tee->slaves[i])) < 0) |
|
196 |
+ goto fail; |
|
197 |
+ av_freep(&slaves[i]); |
|
198 |
+ } |
|
199 |
+ |
|
200 |
+ tee->nb_slaves = nb_slaves; |
|
201 |
+ return 0; |
|
202 |
+ |
|
203 |
+fail: |
|
204 |
+ for (i = 0; i < nb_slaves; i++) |
|
205 |
+ av_freep(&slaves[i]); |
|
206 |
+ close_slaves(avf); |
|
207 |
+ return ret; |
|
208 |
+} |
|
209 |
+ |
|
210 |
+static int tee_write_trailer(AVFormatContext *avf) |
|
211 |
+{ |
|
212 |
+ TeeContext *tee = avf->priv_data; |
|
213 |
+ AVFormatContext *avf2; |
|
214 |
+ int ret_all = 0, ret; |
|
215 |
+ unsigned i; |
|
216 |
+ |
|
217 |
+ for (i = 0; i < tee->nb_slaves; i++) { |
|
218 |
+ avf2 = tee->slaves[i]; |
|
219 |
+ if ((ret = av_write_trailer(avf2)) < 0) |
|
220 |
+ if (!ret_all) |
|
221 |
+ ret_all = ret; |
|
222 |
+ if (!(avf2->oformat->flags & AVFMT_NOFILE)) { |
|
223 |
+ if ((ret = avio_close(avf2->pb)) < 0) |
|
224 |
+ if (!ret_all) |
|
225 |
+ ret_all = ret; |
|
226 |
+ avf2->pb = NULL; |
|
227 |
+ } |
|
228 |
+ } |
|
229 |
+ close_slaves(avf); |
|
230 |
+ return ret_all; |
|
231 |
+} |
|
232 |
+ |
|
233 |
+static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt) |
|
234 |
+{ |
|
235 |
+ TeeContext *tee = avf->priv_data; |
|
236 |
+ AVFormatContext *avf2; |
|
237 |
+ AVPacket pkt2; |
|
238 |
+ int ret_all = 0, ret; |
|
239 |
+ unsigned i, s; |
|
240 |
+ AVRational tb, tb2; |
|
241 |
+ |
|
242 |
+ for (i = 0; i < tee->nb_slaves; i++) { |
|
243 |
+ avf2 = tee->slaves[i]; |
|
244 |
+ s = pkt->stream_index; |
|
245 |
+ if (s >= avf2->nb_streams) { |
|
246 |
+ if (!ret_all) |
|
247 |
+ ret_all = AVERROR(EINVAL); |
|
248 |
+ continue; |
|
249 |
+ } |
|
250 |
+ if ((ret = av_copy_packet(&pkt2, pkt)) < 0 || |
|
251 |
+ (ret = av_dup_packet(&pkt2))< 0) |
|
252 |
+ if (!ret_all) { |
|
253 |
+ ret = ret_all; |
|
254 |
+ continue; |
|
255 |
+ } |
|
256 |
+ tb = avf ->streams[s]->time_base; |
|
257 |
+ tb2 = avf2->streams[s]->time_base; |
|
258 |
+ pkt2.pts = av_rescale_q(pkt->pts, tb, tb2); |
|
259 |
+ pkt2.dts = av_rescale_q(pkt->dts, tb, tb2); |
|
260 |
+ pkt2.duration = av_rescale_q(pkt->duration, tb, tb2); |
|
261 |
+ if ((ret = av_interleaved_write_frame(avf2, &pkt2)) < 0) |
|
262 |
+ if (!ret_all) |
|
263 |
+ ret_all = ret; |
|
264 |
+ } |
|
265 |
+ return ret_all; |
|
266 |
+} |
|
267 |
+ |
|
268 |
+AVOutputFormat ff_tee_muxer = { |
|
269 |
+ .name = "tee", |
|
270 |
+ .long_name = NULL_IF_CONFIG_SMALL("Multiple muxer tee"), |
|
271 |
+ .priv_data_size = sizeof(TeeContext), |
|
272 |
+ .write_header = tee_write_header, |
|
273 |
+ .write_trailer = tee_write_trailer, |
|
274 |
+ .write_packet = tee_write_packet, |
|
275 |
+ .priv_class = &tee_muxer_class, |
|
276 |
+ .flags = AVFMT_NOFILE, |
|
277 |
+}; |
... | ... |
@@ -30,8 +30,8 @@ |
30 | 30 |
#include "libavutil/avutil.h" |
31 | 31 |
|
32 | 32 |
#define LIBAVFORMAT_VERSION_MAJOR 54 |
33 |
-#define LIBAVFORMAT_VERSION_MINOR 61 |
|
34 |
-#define LIBAVFORMAT_VERSION_MICRO 104 |
|
33 |
+#define LIBAVFORMAT_VERSION_MINOR 62 |
|
34 |
+#define LIBAVFORMAT_VERSION_MICRO 100 |
|
35 | 35 |
|
36 | 36 |
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ |
37 | 37 |
LIBAVFORMAT_VERSION_MINOR, \ |