Line |
Branch |
Exec |
Source |
1 |
|
|
/* |
2 |
|
|
* Tee pseudo-muxer |
3 |
|
|
* Copyright (c) 2012 Nicolas George |
4 |
|
|
* |
5 |
|
|
* This file is part of FFmpeg. |
6 |
|
|
* |
7 |
|
|
* FFmpeg is free software; you can redistribute it and/or |
8 |
|
|
* modify it under the terms of the GNU Lesser General Public License |
9 |
|
|
* as published by the Free Software Foundation; either |
10 |
|
|
* version 2.1 of the License, or (at your option) any later version. |
11 |
|
|
* |
12 |
|
|
* FFmpeg is distributed in the hope that it will be useful, |
13 |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
|
|
* GNU Lesser General Public License for more details. |
16 |
|
|
* |
17 |
|
|
* You should have received a copy of the GNU Lesser General Public License |
18 |
|
|
* along with FFmpeg; if not, write to the Free Software * Foundation, Inc., |
19 |
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 |
|
|
*/ |
21 |
|
|
|
22 |
|
|
|
23 |
|
|
#include "libavutil/avutil.h" |
24 |
|
|
#include "libavutil/avstring.h" |
25 |
|
|
#include "libavutil/mem.h" |
26 |
|
|
#include "libavutil/opt.h" |
27 |
|
|
#include "libavcodec/bsf.h" |
28 |
|
|
#include "internal.h" |
29 |
|
|
#include "avformat.h" |
30 |
|
|
#include "mux.h" |
31 |
|
|
#include "tee_common.h" |
32 |
|
|
|
33 |
|
|
typedef enum { |
34 |
|
|
ON_SLAVE_FAILURE_ABORT = 1, |
35 |
|
|
ON_SLAVE_FAILURE_IGNORE = 2 |
36 |
|
|
} SlaveFailurePolicy; |
37 |
|
|
|
38 |
|
|
#define DEFAULT_SLAVE_FAILURE_POLICY ON_SLAVE_FAILURE_ABORT |
39 |
|
|
|
40 |
|
|
typedef struct { |
41 |
|
|
AVFormatContext *avf; |
42 |
|
|
AVBSFContext **bsfs; ///< bitstream filters per stream |
43 |
|
|
|
44 |
|
|
SlaveFailurePolicy on_fail; |
45 |
|
|
int use_fifo; |
46 |
|
|
AVDictionary *fifo_options; |
47 |
|
|
|
48 |
|
|
/** map from input to output streams indexes, |
49 |
|
|
* disabled output streams are set to -1 */ |
50 |
|
|
int *stream_map; |
51 |
|
|
int header_written; |
52 |
|
|
} TeeSlave; |
53 |
|
|
|
54 |
|
|
typedef struct TeeContext { |
55 |
|
|
const AVClass *class; |
56 |
|
|
unsigned nb_slaves; |
57 |
|
|
unsigned nb_alive; |
58 |
|
|
TeeSlave *slaves; |
59 |
|
|
int use_fifo; |
60 |
|
|
AVDictionary *fifo_options; |
61 |
|
|
} TeeContext; |
62 |
|
|
|
63 |
|
|
static const char *const slave_delim = "|"; |
64 |
|
|
static const char *const slave_bsfs_spec_sep = "/"; |
65 |
|
|
static const char *const slave_select_sep = ","; |
66 |
|
|
|
67 |
|
|
#define OFFSET(x) offsetof(TeeContext, x) |
68 |
|
|
static const AVOption options[] = { |
69 |
|
|
{"use_fifo", "Use fifo pseudo-muxer to separate actual muxers from encoder", |
70 |
|
|
OFFSET(use_fifo), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, |
71 |
|
|
{"fifo_options", "fifo pseudo-muxer options", OFFSET(fifo_options), |
72 |
|
|
AV_OPT_TYPE_DICT, {.str = NULL}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM}, |
73 |
|
|
{NULL} |
74 |
|
|
}; |
75 |
|
|
|
76 |
|
|
static const AVClass tee_muxer_class = { |
77 |
|
|
.class_name = "Tee muxer", |
78 |
|
|
.item_name = av_default_item_name, |
79 |
|
|
.option = options, |
80 |
|
|
.version = LIBAVUTIL_VERSION_INT, |
81 |
|
|
}; |
82 |
|
|
|
83 |
|
✗ |
static inline int parse_slave_failure_policy_option(const char *opt, TeeSlave *tee_slave) |
84 |
|
|
{ |
85 |
|
✗ |
if (!opt) { |
86 |
|
✗ |
tee_slave->on_fail = DEFAULT_SLAVE_FAILURE_POLICY; |
87 |
|
✗ |
return 0; |
88 |
|
✗ |
} else if (!av_strcasecmp("abort", opt)) { |
89 |
|
✗ |
tee_slave->on_fail = ON_SLAVE_FAILURE_ABORT; |
90 |
|
✗ |
return 0; |
91 |
|
✗ |
} else if (!av_strcasecmp("ignore", opt)) { |
92 |
|
✗ |
tee_slave->on_fail = ON_SLAVE_FAILURE_IGNORE; |
93 |
|
✗ |
return 0; |
94 |
|
|
} |
95 |
|
|
/* Set failure behaviour to abort, so invalid option error will not be ignored */ |
96 |
|
✗ |
tee_slave->on_fail = ON_SLAVE_FAILURE_ABORT; |
97 |
|
✗ |
return AVERROR(EINVAL); |
98 |
|
|
} |
99 |
|
|
|
100 |
|
✗ |
static int parse_slave_fifo_policy(const char *use_fifo, TeeSlave *tee_slave) |
101 |
|
|
{ |
102 |
|
|
/*TODO - change this to use proper function for parsing boolean |
103 |
|
|
* options when there is one */ |
104 |
|
✗ |
if (av_match_name(use_fifo, "true,y,yes,enable,enabled,on,1")) { |
105 |
|
✗ |
tee_slave->use_fifo = 1; |
106 |
|
✗ |
} else if (av_match_name(use_fifo, "false,n,no,disable,disabled,off,0")) { |
107 |
|
✗ |
tee_slave->use_fifo = 0; |
108 |
|
|
} else { |
109 |
|
✗ |
return AVERROR(EINVAL); |
110 |
|
|
} |
111 |
|
✗ |
return 0; |
112 |
|
|
} |
113 |
|
|
|
114 |
|
✗ |
static int parse_slave_fifo_options(const char *fifo_options, TeeSlave *tee_slave) |
115 |
|
|
{ |
116 |
|
✗ |
return av_dict_parse_string(&tee_slave->fifo_options, fifo_options, "=", ":", 0); |
117 |
|
|
} |
118 |
|
|
|
119 |
|
✗ |
static int close_slave(TeeSlave *tee_slave) |
120 |
|
|
{ |
121 |
|
|
AVFormatContext *avf; |
122 |
|
✗ |
int ret = 0; |
123 |
|
|
|
124 |
|
✗ |
av_dict_free(&tee_slave->fifo_options); |
125 |
|
✗ |
avf = tee_slave->avf; |
126 |
|
✗ |
if (!avf) |
127 |
|
✗ |
return 0; |
128 |
|
|
|
129 |
|
✗ |
if (tee_slave->header_written) |
130 |
|
✗ |
ret = av_write_trailer(avf); |
131 |
|
|
|
132 |
|
✗ |
if (tee_slave->bsfs) { |
133 |
|
✗ |
for (unsigned i = 0; i < avf->nb_streams; ++i) |
134 |
|
✗ |
av_bsf_free(&tee_slave->bsfs[i]); |
135 |
|
|
} |
136 |
|
✗ |
av_freep(&tee_slave->stream_map); |
137 |
|
✗ |
av_freep(&tee_slave->bsfs); |
138 |
|
|
|
139 |
|
✗ |
ff_format_io_close(avf, &avf->pb); |
140 |
|
✗ |
avformat_free_context(avf); |
141 |
|
✗ |
tee_slave->avf = NULL; |
142 |
|
✗ |
return ret; |
143 |
|
|
} |
144 |
|
|
|
145 |
|
✗ |
static void close_slaves(AVFormatContext *avf) |
146 |
|
|
{ |
147 |
|
✗ |
TeeContext *tee = avf->priv_data; |
148 |
|
|
|
149 |
|
✗ |
for (unsigned i = 0; i < tee->nb_slaves; i++) { |
150 |
|
✗ |
close_slave(&tee->slaves[i]); |
151 |
|
|
} |
152 |
|
✗ |
av_freep(&tee->slaves); |
153 |
|
✗ |
} |
154 |
|
|
|
155 |
|
✗ |
static int open_slave(AVFormatContext *avf, char *slave, TeeSlave *tee_slave) |
156 |
|
|
{ |
157 |
|
|
int ret; |
158 |
|
✗ |
AVDictionary *options = NULL, *bsf_options = NULL; |
159 |
|
|
const AVDictionaryEntry *entry; |
160 |
|
|
char *filename; |
161 |
|
✗ |
char *format = NULL, *select = NULL; |
162 |
|
✗ |
AVFormatContext *avf2 = NULL; |
163 |
|
|
int stream_count; |
164 |
|
|
int fullret; |
165 |
|
✗ |
char *subselect = NULL, *next_subselect = NULL, *first_subselect = NULL, *tmp_select = NULL; |
166 |
|
|
|
167 |
|
✗ |
if ((ret = ff_tee_parse_slave_options(avf, slave, &options, &filename)) < 0) |
168 |
|
✗ |
return ret; |
169 |
|
|
|
170 |
|
|
#define CONSUME_OPTION(option, field, action) do { \ |
171 |
|
|
AVDictionaryEntry *en = av_dict_get(options, option, NULL, 0); \ |
172 |
|
|
if (en) { \ |
173 |
|
|
field = en->value; \ |
174 |
|
|
{ action } \ |
175 |
|
|
av_dict_set(&options, option, NULL, 0); \ |
176 |
|
|
} \ |
177 |
|
|
} while (0) |
178 |
|
|
#define STEAL_OPTION(option, field) \ |
179 |
|
|
CONSUME_OPTION(option, field, \ |
180 |
|
|
en->value = NULL; /* prevent it from being freed */) |
181 |
|
|
#define PROCESS_OPTION(option, function, on_error) do { \ |
182 |
|
|
const char *value; \ |
183 |
|
|
CONSUME_OPTION(option, value, if ((ret = function) < 0) \ |
184 |
|
|
{ { on_error } goto end; }); \ |
185 |
|
|
} while (0) |
186 |
|
|
|
187 |
|
✗ |
STEAL_OPTION("f", format); |
188 |
|
✗ |
STEAL_OPTION("select", select); |
189 |
|
✗ |
PROCESS_OPTION("onfail", |
190 |
|
|
parse_slave_failure_policy_option(value, tee_slave), |
191 |
|
|
av_log(avf, AV_LOG_ERROR, "Invalid onfail option value, " |
192 |
|
|
"valid options are 'abort' and 'ignore'\n");); |
193 |
|
✗ |
PROCESS_OPTION("use_fifo", |
194 |
|
|
parse_slave_fifo_policy(value, tee_slave), |
195 |
|
|
av_log(avf, AV_LOG_ERROR, "Error parsing fifo options: %s\n", |
196 |
|
|
av_err2str(ret));); |
197 |
|
✗ |
PROCESS_OPTION("fifo_options", |
198 |
|
|
parse_slave_fifo_options(value, tee_slave), ;); |
199 |
|
✗ |
entry = NULL; |
200 |
|
✗ |
while ((entry = av_dict_get(options, "bsfs", entry, AV_DICT_IGNORE_SUFFIX))) { |
201 |
|
|
/* trim out strlen("bsfs") characters from key */ |
202 |
|
✗ |
av_dict_set(&bsf_options, entry->key + 4, entry->value, 0); |
203 |
|
✗ |
av_dict_set(&options, entry->key, NULL, 0); |
204 |
|
|
} |
205 |
|
|
|
206 |
|
✗ |
if (tee_slave->use_fifo) { |
207 |
|
|
|
208 |
|
✗ |
if (options) { |
209 |
|
✗ |
char *format_options_str = NULL; |
210 |
|
✗ |
ret = av_dict_get_string(options, &format_options_str, '=', ':'); |
211 |
|
✗ |
if (ret < 0) |
212 |
|
✗ |
goto end; |
213 |
|
|
|
214 |
|
✗ |
ret = av_dict_set(&tee_slave->fifo_options, "format_opts", format_options_str, |
215 |
|
|
AV_DICT_DONT_STRDUP_VAL); |
216 |
|
✗ |
if (ret < 0) |
217 |
|
✗ |
goto end; |
218 |
|
|
} |
219 |
|
|
|
220 |
|
✗ |
if (format) { |
221 |
|
✗ |
ret = av_dict_set(&tee_slave->fifo_options, "fifo_format", format, |
222 |
|
|
AV_DICT_DONT_STRDUP_VAL); |
223 |
|
✗ |
format = NULL; |
224 |
|
✗ |
if (ret < 0) |
225 |
|
✗ |
goto end; |
226 |
|
|
} |
227 |
|
|
|
228 |
|
✗ |
av_dict_free(&options); |
229 |
|
✗ |
options = tee_slave->fifo_options; |
230 |
|
✗ |
tee_slave->fifo_options = NULL; |
231 |
|
|
} |
232 |
|
✗ |
ret = avformat_alloc_output_context2(&avf2, NULL, |
233 |
|
✗ |
tee_slave->use_fifo ? "fifo" :format, filename); |
234 |
|
✗ |
if (ret < 0) |
235 |
|
✗ |
goto end; |
236 |
|
✗ |
tee_slave->avf = avf2; |
237 |
|
✗ |
av_dict_copy(&avf2->metadata, avf->metadata, 0); |
238 |
|
✗ |
avf2->opaque = avf->opaque; |
239 |
|
✗ |
avf2->io_open = avf->io_open; |
240 |
|
✗ |
avf2->io_close2 = avf->io_close2; |
241 |
|
✗ |
avf2->interrupt_callback = avf->interrupt_callback; |
242 |
|
✗ |
avf2->flags = avf->flags; |
243 |
|
✗ |
avf2->strict_std_compliance = avf->strict_std_compliance; |
244 |
|
|
|
245 |
|
✗ |
tee_slave->stream_map = av_calloc(avf->nb_streams, sizeof(*tee_slave->stream_map)); |
246 |
|
✗ |
if (!tee_slave->stream_map) { |
247 |
|
✗ |
ret = AVERROR(ENOMEM); |
248 |
|
✗ |
goto end; |
249 |
|
|
} |
250 |
|
|
|
251 |
|
✗ |
stream_count = 0; |
252 |
|
✗ |
for (unsigned i = 0; i < avf->nb_streams; i++) { |
253 |
|
✗ |
const AVStream *st = avf->streams[i]; |
254 |
|
|
AVStream *st2; |
255 |
|
✗ |
if (select) { |
256 |
|
✗ |
tmp_select = av_strdup(select); // av_strtok is destructive so we regenerate it in each loop |
257 |
|
✗ |
if (!tmp_select) { |
258 |
|
✗ |
ret = AVERROR(ENOMEM); |
259 |
|
✗ |
goto end; |
260 |
|
|
} |
261 |
|
✗ |
fullret = 0; |
262 |
|
✗ |
first_subselect = tmp_select; |
263 |
|
✗ |
next_subselect = NULL; |
264 |
|
✗ |
while (subselect = av_strtok(first_subselect, slave_select_sep, &next_subselect)) { |
265 |
|
✗ |
first_subselect = NULL; |
266 |
|
|
|
267 |
|
✗ |
ret = avformat_match_stream_specifier(avf, avf->streams[i], subselect); |
268 |
|
✗ |
if (ret < 0) { |
269 |
|
✗ |
av_log(avf, AV_LOG_ERROR, |
270 |
|
|
"Invalid stream specifier '%s' for output '%s'\n", |
271 |
|
|
subselect, slave); |
272 |
|
✗ |
goto end; |
273 |
|
|
} |
274 |
|
✗ |
if (ret != 0) { |
275 |
|
✗ |
fullret = 1; // match |
276 |
|
✗ |
break; |
277 |
|
|
} |
278 |
|
|
} |
279 |
|
✗ |
av_freep(&tmp_select); |
280 |
|
|
|
281 |
|
✗ |
if (fullret == 0) { /* no match */ |
282 |
|
✗ |
tee_slave->stream_map[i] = -1; |
283 |
|
✗ |
continue; |
284 |
|
|
} |
285 |
|
|
} |
286 |
|
✗ |
tee_slave->stream_map[i] = stream_count++; |
287 |
|
|
|
288 |
|
✗ |
st2 = ff_stream_clone(avf2, st); |
289 |
|
✗ |
if (!st2) { |
290 |
|
✗ |
ret = AVERROR(ENOMEM); |
291 |
|
✗ |
goto end; |
292 |
|
|
} |
293 |
|
|
} |
294 |
|
|
|
295 |
|
✗ |
ret = ff_format_output_open(avf2, filename, &options); |
296 |
|
✗ |
if (ret < 0) { |
297 |
|
✗ |
av_log(avf, AV_LOG_ERROR, "Slave '%s': error opening: %s\n", slave, |
298 |
|
✗ |
av_err2str(ret)); |
299 |
|
✗ |
goto end; |
300 |
|
|
} |
301 |
|
|
|
302 |
|
✗ |
if ((ret = avformat_write_header(avf2, &options)) < 0) { |
303 |
|
✗ |
av_log(avf, AV_LOG_ERROR, "Slave '%s': error writing header: %s\n", |
304 |
|
✗ |
slave, av_err2str(ret)); |
305 |
|
✗ |
goto end; |
306 |
|
|
} |
307 |
|
✗ |
tee_slave->header_written = 1; |
308 |
|
|
|
309 |
|
✗ |
tee_slave->bsfs = av_calloc(avf2->nb_streams, sizeof(*tee_slave->bsfs)); |
310 |
|
✗ |
if (!tee_slave->bsfs) { |
311 |
|
✗ |
ret = AVERROR(ENOMEM); |
312 |
|
✗ |
goto end; |
313 |
|
|
} |
314 |
|
|
|
315 |
|
✗ |
entry = NULL; |
316 |
|
✗ |
while (entry = av_dict_iterate(bsf_options, NULL)) { |
317 |
|
✗ |
const char *spec = entry->key; |
318 |
|
✗ |
if (*spec) { |
319 |
|
✗ |
if (strspn(spec, slave_bsfs_spec_sep) != 1) { |
320 |
|
✗ |
av_log(avf, AV_LOG_ERROR, |
321 |
|
|
"Specifier separator in '%s' is '%c', but only characters '%s' " |
322 |
|
✗ |
"are allowed\n", entry->key, *spec, slave_bsfs_spec_sep); |
323 |
|
✗ |
ret = AVERROR(EINVAL); |
324 |
|
✗ |
goto end; |
325 |
|
|
} |
326 |
|
✗ |
spec++; /* consume separator */ |
327 |
|
|
} |
328 |
|
|
|
329 |
|
✗ |
for (unsigned i = 0; i < avf2->nb_streams; i++) { |
330 |
|
✗ |
ret = avformat_match_stream_specifier(avf2, avf2->streams[i], spec); |
331 |
|
✗ |
if (ret < 0) { |
332 |
|
✗ |
av_log(avf, AV_LOG_ERROR, |
333 |
|
|
"Invalid stream specifier '%s' in bsfs option '%s' for slave " |
334 |
|
✗ |
"output '%s'\n", spec, entry->key, filename); |
335 |
|
✗ |
goto end; |
336 |
|
|
} |
337 |
|
|
|
338 |
|
✗ |
if (ret > 0) { |
339 |
|
✗ |
av_log(avf, AV_LOG_DEBUG, "spec:%s bsfs:%s matches stream %d of slave " |
340 |
|
✗ |
"output '%s'\n", spec, entry->value, i, filename); |
341 |
|
✗ |
if (tee_slave->bsfs[i]) { |
342 |
|
✗ |
av_log(avf, AV_LOG_WARNING, |
343 |
|
|
"Duplicate bsfs specification associated to stream %d of slave " |
344 |
|
|
"output '%s', filters will be ignored\n", i, filename); |
345 |
|
✗ |
continue; |
346 |
|
|
} |
347 |
|
✗ |
ret = av_bsf_list_parse_str(entry->value, &tee_slave->bsfs[i]); |
348 |
|
✗ |
if (ret < 0) { |
349 |
|
✗ |
av_log(avf, AV_LOG_ERROR, |
350 |
|
|
"Error parsing bitstream filter sequence '%s' associated to " |
351 |
|
✗ |
"stream %d of slave output '%s'\n", entry->value, i, filename); |
352 |
|
✗ |
goto end; |
353 |
|
|
} |
354 |
|
|
} |
355 |
|
|
} |
356 |
|
|
|
357 |
|
✗ |
av_dict_set(&bsf_options, entry->key, NULL, 0); |
358 |
|
|
} |
359 |
|
|
|
360 |
|
✗ |
for (unsigned i = 0; i < avf->nb_streams; i++){ |
361 |
|
✗ |
int target_stream = tee_slave->stream_map[i]; |
362 |
|
✗ |
if (target_stream < 0) |
363 |
|
✗ |
continue; |
364 |
|
|
|
365 |
|
✗ |
if (!tee_slave->bsfs[target_stream]) { |
366 |
|
|
/* Add pass-through bitstream filter */ |
367 |
|
✗ |
ret = av_bsf_get_null_filter(&tee_slave->bsfs[target_stream]); |
368 |
|
✗ |
if (ret < 0) { |
369 |
|
✗ |
av_log(avf, AV_LOG_ERROR, |
370 |
|
|
"Failed to create pass-through bitstream filter: %s\n", |
371 |
|
✗ |
av_err2str(ret)); |
372 |
|
✗ |
goto end; |
373 |
|
|
} |
374 |
|
|
} |
375 |
|
|
|
376 |
|
✗ |
tee_slave->bsfs[target_stream]->time_base_in = avf->streams[i]->time_base; |
377 |
|
✗ |
ret = avcodec_parameters_copy(tee_slave->bsfs[target_stream]->par_in, |
378 |
|
✗ |
avf->streams[i]->codecpar); |
379 |
|
✗ |
if (ret < 0) |
380 |
|
✗ |
goto end; |
381 |
|
|
|
382 |
|
✗ |
ret = av_bsf_init(tee_slave->bsfs[target_stream]); |
383 |
|
✗ |
if (ret < 0) { |
384 |
|
✗ |
av_log(avf, AV_LOG_ERROR, |
385 |
|
|
"Failed to initialize bitstream filter(s): %s\n", |
386 |
|
✗ |
av_err2str(ret)); |
387 |
|
✗ |
goto end; |
388 |
|
|
} |
389 |
|
|
} |
390 |
|
|
|
391 |
|
✗ |
if (options) { |
392 |
|
✗ |
entry = NULL; |
393 |
|
✗ |
while ((entry = av_dict_iterate(options, entry))) |
394 |
|
✗ |
av_log(avf2, AV_LOG_ERROR, "Unknown option '%s'\n", entry->key); |
395 |
|
✗ |
ret = AVERROR_OPTION_NOT_FOUND; |
396 |
|
✗ |
goto end; |
397 |
|
|
} |
398 |
|
|
|
399 |
|
✗ |
end: |
400 |
|
✗ |
av_free(format); |
401 |
|
✗ |
av_free(select); |
402 |
|
✗ |
av_dict_free(&options); |
403 |
|
✗ |
av_dict_free(&bsf_options); |
404 |
|
✗ |
av_freep(&tmp_select); |
405 |
|
✗ |
return ret; |
406 |
|
|
} |
407 |
|
|
|
408 |
|
✗ |
static void log_slave(TeeSlave *slave, void *log_ctx, int log_level) |
409 |
|
|
{ |
410 |
|
✗ |
av_log(log_ctx, log_level, "filename:'%s' format:%s\n", |
411 |
|
✗ |
slave->avf->url, slave->avf->oformat->name); |
412 |
|
✗ |
for (unsigned i = 0; i < slave->avf->nb_streams; i++) { |
413 |
|
✗ |
AVStream *st = slave->avf->streams[i]; |
414 |
|
✗ |
AVBSFContext *bsf = slave->bsfs[i]; |
415 |
|
|
const char *bsf_name; |
416 |
|
|
|
417 |
|
✗ |
av_log(log_ctx, log_level, " stream:%d codec:%s type:%s", |
418 |
|
✗ |
i, avcodec_get_name(st->codecpar->codec_id), |
419 |
|
✗ |
av_get_media_type_string(st->codecpar->codec_type)); |
420 |
|
|
|
421 |
|
✗ |
bsf_name = bsf->filter->priv_class ? |
422 |
|
✗ |
bsf->filter->priv_class->item_name(bsf) : bsf->filter->name; |
423 |
|
✗ |
av_log(log_ctx, log_level, " bsfs: %s\n", bsf_name); |
424 |
|
|
} |
425 |
|
✗ |
} |
426 |
|
|
|
427 |
|
✗ |
static int tee_process_slave_failure(AVFormatContext *avf, unsigned slave_idx, int err_n) |
428 |
|
|
{ |
429 |
|
✗ |
TeeContext *tee = avf->priv_data; |
430 |
|
✗ |
TeeSlave *tee_slave = &tee->slaves[slave_idx]; |
431 |
|
|
|
432 |
|
✗ |
tee->nb_alive--; |
433 |
|
|
|
434 |
|
✗ |
close_slave(tee_slave); |
435 |
|
|
|
436 |
|
✗ |
if (!tee->nb_alive) { |
437 |
|
✗ |
av_log(avf, AV_LOG_ERROR, "All tee outputs failed.\n"); |
438 |
|
✗ |
return err_n; |
439 |
|
✗ |
} else if (tee_slave->on_fail == ON_SLAVE_FAILURE_ABORT) { |
440 |
|
✗ |
av_log(avf, AV_LOG_ERROR, "Slave muxer #%u failed, aborting.\n", slave_idx); |
441 |
|
✗ |
return err_n; |
442 |
|
|
} else { |
443 |
|
✗ |
av_log(avf, AV_LOG_ERROR, "Slave muxer #%u failed: %s, continuing with %u/%u slaves.\n", |
444 |
|
✗ |
slave_idx, av_err2str(err_n), tee->nb_alive, tee->nb_slaves); |
445 |
|
✗ |
return 0; |
446 |
|
|
} |
447 |
|
|
} |
448 |
|
|
|
449 |
|
✗ |
static int tee_write_header(AVFormatContext *avf) |
450 |
|
|
{ |
451 |
|
✗ |
TeeContext *tee = avf->priv_data; |
452 |
|
✗ |
unsigned nb_slaves = 0; |
453 |
|
✗ |
const char *filename = avf->url; |
454 |
|
✗ |
char **slaves = NULL; |
455 |
|
|
int ret; |
456 |
|
|
|
457 |
|
✗ |
while (*filename) { |
458 |
|
✗ |
char *slave = av_get_token(&filename, slave_delim); |
459 |
|
✗ |
if (!slave) { |
460 |
|
✗ |
ret = AVERROR(ENOMEM); |
461 |
|
✗ |
goto fail; |
462 |
|
|
} |
463 |
|
✗ |
ret = av_dynarray_add_nofree(&slaves, &nb_slaves, slave); |
464 |
|
✗ |
if (ret < 0) { |
465 |
|
✗ |
av_free(slave); |
466 |
|
✗ |
goto fail; |
467 |
|
|
} |
468 |
|
✗ |
if (strspn(filename, slave_delim)) |
469 |
|
✗ |
filename++; |
470 |
|
|
} |
471 |
|
|
|
472 |
|
✗ |
if (!FF_ALLOCZ_TYPED_ARRAY(tee->slaves, nb_slaves)) { |
473 |
|
✗ |
ret = AVERROR(ENOMEM); |
474 |
|
✗ |
goto fail; |
475 |
|
|
} |
476 |
|
✗ |
tee->nb_slaves = tee->nb_alive = nb_slaves; |
477 |
|
|
|
478 |
|
✗ |
for (unsigned i = 0; i < nb_slaves; i++) { |
479 |
|
|
|
480 |
|
✗ |
tee->slaves[i].use_fifo = tee->use_fifo; |
481 |
|
✗ |
ret = av_dict_copy(&tee->slaves[i].fifo_options, tee->fifo_options, 0); |
482 |
|
✗ |
if (ret < 0) |
483 |
|
✗ |
goto fail; |
484 |
|
|
|
485 |
|
✗ |
if ((ret = open_slave(avf, slaves[i], &tee->slaves[i])) < 0) { |
486 |
|
✗ |
ret = tee_process_slave_failure(avf, i, ret); |
487 |
|
✗ |
if (ret < 0) |
488 |
|
✗ |
goto fail; |
489 |
|
|
} else { |
490 |
|
✗ |
log_slave(&tee->slaves[i], avf, AV_LOG_VERBOSE); |
491 |
|
|
} |
492 |
|
✗ |
av_freep(&slaves[i]); |
493 |
|
|
} |
494 |
|
|
|
495 |
|
✗ |
for (unsigned i = 0; i < avf->nb_streams; i++) { |
496 |
|
✗ |
int mapped = 0; |
497 |
|
✗ |
for (unsigned j = 0; j < tee->nb_slaves; j++) |
498 |
|
✗ |
if (tee->slaves[j].avf) |
499 |
|
✗ |
mapped += tee->slaves[j].stream_map[i] >= 0; |
500 |
|
✗ |
if (!mapped) |
501 |
|
✗ |
av_log(avf, AV_LOG_WARNING, "Input stream #%d is not mapped " |
502 |
|
|
"to any slave.\n", i); |
503 |
|
|
} |
504 |
|
✗ |
av_free(slaves); |
505 |
|
✗ |
return 0; |
506 |
|
|
|
507 |
|
✗ |
fail: |
508 |
|
✗ |
for (unsigned i = 0; i < nb_slaves; i++) |
509 |
|
✗ |
av_freep(&slaves[i]); |
510 |
|
✗ |
close_slaves(avf); |
511 |
|
✗ |
av_free(slaves); |
512 |
|
✗ |
return ret; |
513 |
|
|
} |
514 |
|
|
|
515 |
|
✗ |
static int tee_write_trailer(AVFormatContext *avf) |
516 |
|
|
{ |
517 |
|
✗ |
TeeContext *tee = avf->priv_data; |
518 |
|
✗ |
int ret_all = 0, ret; |
519 |
|
|
|
520 |
|
✗ |
for (unsigned i = 0; i < tee->nb_slaves; i++) { |
521 |
|
✗ |
if ((ret = close_slave(&tee->slaves[i])) < 0) { |
522 |
|
✗ |
ret = tee_process_slave_failure(avf, i, ret); |
523 |
|
✗ |
if (!ret_all && ret < 0) |
524 |
|
✗ |
ret_all = ret; |
525 |
|
|
} |
526 |
|
|
} |
527 |
|
✗ |
av_freep(&tee->slaves); |
528 |
|
✗ |
return ret_all; |
529 |
|
|
} |
530 |
|
|
|
531 |
|
✗ |
static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt) |
532 |
|
|
{ |
533 |
|
✗ |
TeeContext *tee = avf->priv_data; |
534 |
|
✗ |
AVPacket *const pkt2 = ffformatcontext(avf)->pkt; |
535 |
|
✗ |
int ret_all = 0, ret; |
536 |
|
|
unsigned s; |
537 |
|
|
int s2; |
538 |
|
|
|
539 |
|
✗ |
for (unsigned i = 0; i < tee->nb_slaves; i++) { |
540 |
|
✗ |
AVFormatContext *avf2 = tee->slaves[i].avf; |
541 |
|
|
AVBSFContext *bsfs; |
542 |
|
|
|
543 |
|
✗ |
if (!avf2) |
544 |
|
✗ |
continue; |
545 |
|
|
|
546 |
|
|
/* Flush slave if pkt is NULL*/ |
547 |
|
✗ |
if (!pkt) { |
548 |
|
✗ |
ret = av_interleaved_write_frame(avf2, NULL); |
549 |
|
✗ |
if (ret < 0) { |
550 |
|
✗ |
ret = tee_process_slave_failure(avf, i, ret); |
551 |
|
✗ |
if (!ret_all && ret < 0) |
552 |
|
✗ |
ret_all = ret; |
553 |
|
|
} |
554 |
|
✗ |
continue; |
555 |
|
|
} |
556 |
|
|
|
557 |
|
✗ |
s = pkt->stream_index; |
558 |
|
✗ |
s2 = tee->slaves[i].stream_map[s]; |
559 |
|
✗ |
if (s2 < 0) |
560 |
|
✗ |
continue; |
561 |
|
|
|
562 |
|
✗ |
if ((ret = av_packet_ref(pkt2, pkt)) < 0) { |
563 |
|
✗ |
if (!ret_all) |
564 |
|
✗ |
ret_all = ret; |
565 |
|
✗ |
continue; |
566 |
|
|
} |
567 |
|
✗ |
bsfs = tee->slaves[i].bsfs[s2]; |
568 |
|
✗ |
pkt2->stream_index = s2; |
569 |
|
|
|
570 |
|
✗ |
ret = av_bsf_send_packet(bsfs, pkt2); |
571 |
|
✗ |
if (ret < 0) { |
572 |
|
✗ |
av_packet_unref(pkt2); |
573 |
|
✗ |
av_log(avf, AV_LOG_ERROR, "Error while sending packet to bitstream filter: %s\n", |
574 |
|
✗ |
av_err2str(ret)); |
575 |
|
✗ |
ret = tee_process_slave_failure(avf, i, ret); |
576 |
|
✗ |
if (!ret_all && ret < 0) |
577 |
|
✗ |
ret_all = ret; |
578 |
|
|
} |
579 |
|
|
|
580 |
|
|
while(1) { |
581 |
|
✗ |
ret = av_bsf_receive_packet(bsfs, pkt2); |
582 |
|
✗ |
if (ret == AVERROR(EAGAIN)) { |
583 |
|
✗ |
ret = 0; |
584 |
|
✗ |
break; |
585 |
|
✗ |
} else if (ret < 0) { |
586 |
|
✗ |
break; |
587 |
|
|
} |
588 |
|
|
|
589 |
|
✗ |
av_packet_rescale_ts(pkt2, bsfs->time_base_out, |
590 |
|
✗ |
avf2->streams[s2]->time_base); |
591 |
|
✗ |
ret = av_interleaved_write_frame(avf2, pkt2); |
592 |
|
✗ |
if (ret < 0) |
593 |
|
✗ |
break; |
594 |
|
|
}; |
595 |
|
|
|
596 |
|
✗ |
if (ret < 0) { |
597 |
|
✗ |
ret = tee_process_slave_failure(avf, i, ret); |
598 |
|
✗ |
if (!ret_all && ret < 0) |
599 |
|
✗ |
ret_all = ret; |
600 |
|
|
} |
601 |
|
|
} |
602 |
|
✗ |
return ret_all; |
603 |
|
|
} |
604 |
|
|
|
605 |
|
|
const FFOutputFormat ff_tee_muxer = { |
606 |
|
|
.p.name = "tee", |
607 |
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("Multiple muxer tee"), |
608 |
|
|
.priv_data_size = sizeof(TeeContext), |
609 |
|
|
.write_header = tee_write_header, |
610 |
|
|
.write_trailer = tee_write_trailer, |
611 |
|
|
.write_packet = tee_write_packet, |
612 |
|
|
.p.priv_class = &tee_muxer_class, |
613 |
|
|
#if FF_API_ALLOW_FLUSH |
614 |
|
|
.p.flags = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, |
615 |
|
|
#else |
616 |
|
|
.p.flags = AVFMT_NOFILE | AVFMT_TS_NEGATIVE, |
617 |
|
|
#endif |
618 |
|
|
.flags_internal = FF_OFMT_FLAG_ALLOW_FLUSH, |
619 |
|
|
}; |
620 |
|
|
|