Adds per slave option 'onfail' to the tee muxer allowing an output to
fail, so other slave outputs can continue.
Reviewed-by: Nicolas George <george@nsup.org>
Signed-off-by: Jan Sebechlebsky <sebechlebskyjan@gmail.com>
Signed-off-by: Marton Balint <cus@passwd.hu>
... | ... |
@@ -1453,6 +1453,12 @@ Select the streams that should be mapped to the slave output, |
1453 | 1453 |
specified by a stream specifier. If not specified, this defaults to |
1454 | 1454 |
all the input streams. You may use multiple stream specifiers |
1455 | 1455 |
separated by commas (@code{,}) e.g.: @code{a:0,v} |
1456 |
+ |
|
1457 |
+@item onfail |
|
1458 |
+Specify behaviour on output failure. This can be set to either @code{abort} (which is |
|
1459 |
+default) or @code{ignore}. @code{abort} will cause whole process to fail in case of failure |
|
1460 |
+on this slave output. @code{ignore} will ignore failure on this output, so other outputs |
|
1461 |
+will continue without being affected. |
|
1456 | 1462 |
@end table |
1457 | 1463 |
|
1458 | 1464 |
@subsection Examples |
... | ... |
@@ -1467,6 +1473,14 @@ ffmpeg -i ... -c:v libx264 -c:a mp2 -f tee -map 0:v -map 0:a |
1467 | 1467 |
@end example |
1468 | 1468 |
|
1469 | 1469 |
@item |
1470 |
+As above, but continue streaming even if output to local file fails |
|
1471 |
+(for example local drive fills up): |
|
1472 |
+@example |
|
1473 |
+ffmpeg -i ... -c:v libx264 -c:a mp2 -f tee -map 0:v -map 0:a |
|
1474 |
+ "[onfail=ignore]archive-20121107.mkv|[f=mpegts]udp://10.0.1.255:1234/" |
|
1475 |
+@end example |
|
1476 |
+ |
|
1477 |
+@item |
|
1470 | 1478 |
Use @command{ffmpeg} to encode the input, and send the output |
1471 | 1479 |
to three different destinations. The @code{dump_extra} bitstream |
1472 | 1480 |
filter is used to add extradata information to all the output video |
... | ... |
@@ -29,10 +29,19 @@ |
29 | 29 |
|
30 | 30 |
#define MAX_SLAVES 16 |
31 | 31 |
|
32 |
+typedef enum { |
|
33 |
+ ON_SLAVE_FAILURE_ABORT = 1, |
|
34 |
+ ON_SLAVE_FAILURE_IGNORE = 2 |
|
35 |
+} SlaveFailurePolicy; |
|
36 |
+ |
|
37 |
+#define DEFAULT_SLAVE_FAILURE_POLICY ON_SLAVE_FAILURE_ABORT |
|
38 |
+ |
|
32 | 39 |
typedef struct { |
33 | 40 |
AVFormatContext *avf; |
34 | 41 |
AVBitStreamFilterContext **bsfs; ///< bitstream filters per stream |
35 | 42 |
|
43 |
+ SlaveFailurePolicy on_fail; |
|
44 |
+ |
|
36 | 45 |
/** map from input to output streams indexes, |
37 | 46 |
* disabled output streams are set to -1 */ |
38 | 47 |
int *stream_map; |
... | ... |
@@ -42,6 +51,7 @@ typedef struct { |
42 | 42 |
typedef struct TeeContext { |
43 | 43 |
const AVClass *class; |
44 | 44 |
unsigned nb_slaves; |
45 |
+ unsigned nb_alive; |
|
45 | 46 |
TeeSlave slaves[MAX_SLAVES]; |
46 | 47 |
} TeeContext; |
47 | 48 |
|
... | ... |
@@ -136,6 +146,23 @@ end: |
136 | 136 |
return ret; |
137 | 137 |
} |
138 | 138 |
|
139 |
+static inline int parse_slave_failure_policy_option(const char *opt, TeeSlave *tee_slave) |
|
140 |
+{ |
|
141 |
+ if (!opt) { |
|
142 |
+ tee_slave->on_fail = DEFAULT_SLAVE_FAILURE_POLICY; |
|
143 |
+ return 0; |
|
144 |
+ } else if (!av_strcasecmp("abort", opt)) { |
|
145 |
+ tee_slave->on_fail = ON_SLAVE_FAILURE_ABORT; |
|
146 |
+ return 0; |
|
147 |
+ } else if (!av_strcasecmp("ignore", opt)) { |
|
148 |
+ tee_slave->on_fail = ON_SLAVE_FAILURE_IGNORE; |
|
149 |
+ return 0; |
|
150 |
+ } |
|
151 |
+ /* Set failure behaviour to abort, so invalid option error will not be ignored */ |
|
152 |
+ tee_slave->on_fail = ON_SLAVE_FAILURE_ABORT; |
|
153 |
+ return AVERROR(EINVAL); |
|
154 |
+} |
|
155 |
+ |
|
139 | 156 |
static int close_slave(TeeSlave *tee_slave) |
140 | 157 |
{ |
141 | 158 |
AVFormatContext *avf; |
... | ... |
@@ -184,7 +211,7 @@ static int open_slave(AVFormatContext *avf, char *slave, TeeSlave *tee_slave) |
184 | 184 |
AVDictionary *options = NULL; |
185 | 185 |
AVDictionaryEntry *entry; |
186 | 186 |
char *filename; |
187 |
- char *format = NULL, *select = NULL; |
|
187 |
+ char *format = NULL, *select = NULL, *on_fail = NULL; |
|
188 | 188 |
AVFormatContext *avf2 = NULL; |
189 | 189 |
AVStream *st, *st2; |
190 | 190 |
int stream_count; |
... | ... |
@@ -204,6 +231,14 @@ static int open_slave(AVFormatContext *avf, char *slave, TeeSlave *tee_slave) |
204 | 204 |
|
205 | 205 |
STEAL_OPTION("f", format); |
206 | 206 |
STEAL_OPTION("select", select); |
207 |
+ STEAL_OPTION("onfail", on_fail); |
|
208 |
+ |
|
209 |
+ ret = parse_slave_failure_policy_option(on_fail, tee_slave); |
|
210 |
+ if (ret < 0) { |
|
211 |
+ av_log(avf, AV_LOG_ERROR, |
|
212 |
+ "Invalid onfail option value, valid options are 'abort' and 'ignore'\n"); |
|
213 |
+ goto end; |
|
214 |
+ } |
|
207 | 215 |
|
208 | 216 |
ret = avformat_alloc_output_context2(&avf2, NULL, format, filename); |
209 | 217 |
if (ret < 0) |
... | ... |
@@ -351,6 +386,7 @@ static int open_slave(AVFormatContext *avf, char *slave, TeeSlave *tee_slave) |
351 | 351 |
end: |
352 | 352 |
av_free(format); |
353 | 353 |
av_free(select); |
354 |
+ av_free(on_fail); |
|
354 | 355 |
av_dict_free(&options); |
355 | 356 |
av_freep(&tmp_select); |
356 | 357 |
return ret; |
... | ... |
@@ -380,6 +416,28 @@ static void log_slave(TeeSlave *slave, void *log_ctx, int log_level) |
380 | 380 |
} |
381 | 381 |
} |
382 | 382 |
|
383 |
+static int tee_process_slave_failure(AVFormatContext *avf, unsigned slave_idx, int err_n) |
|
384 |
+{ |
|
385 |
+ TeeContext *tee = avf->priv_data; |
|
386 |
+ TeeSlave *tee_slave = &tee->slaves[slave_idx]; |
|
387 |
+ |
|
388 |
+ tee->nb_alive--; |
|
389 |
+ |
|
390 |
+ close_slave(tee_slave); |
|
391 |
+ |
|
392 |
+ if (!tee->nb_alive) { |
|
393 |
+ av_log(avf, AV_LOG_ERROR, "All tee outputs failed.\n"); |
|
394 |
+ return err_n; |
|
395 |
+ } else if (tee_slave->on_fail == ON_SLAVE_FAILURE_ABORT) { |
|
396 |
+ av_log(avf, AV_LOG_ERROR, "Slave muxer #%u failed, aborting.\n", slave_idx); |
|
397 |
+ return err_n; |
|
398 |
+ } else { |
|
399 |
+ av_log(avf, AV_LOG_ERROR, "Slave muxer #%u failed: %s, continuing with %u/%u slaves.\n", |
|
400 |
+ slave_idx, av_err2str(err_n), tee->nb_alive, tee->nb_slaves); |
|
401 |
+ return 0; |
|
402 |
+ } |
|
403 |
+} |
|
404 |
+ |
|
383 | 405 |
static int tee_write_header(AVFormatContext *avf) |
384 | 406 |
{ |
385 | 407 |
TeeContext *tee = avf->priv_data; |
... | ... |
@@ -403,19 +461,24 @@ static int tee_write_header(AVFormatContext *avf) |
403 | 403 |
filename++; |
404 | 404 |
} |
405 | 405 |
|
406 |
- tee->nb_slaves = nb_slaves; |
|
406 |
+ tee->nb_slaves = tee->nb_alive = nb_slaves; |
|
407 | 407 |
|
408 | 408 |
for (i = 0; i < nb_slaves; i++) { |
409 |
- if ((ret = open_slave(avf, slaves[i], &tee->slaves[i])) < 0) |
|
410 |
- goto fail; |
|
411 |
- log_slave(&tee->slaves[i], avf, AV_LOG_VERBOSE); |
|
409 |
+ if ((ret = open_slave(avf, slaves[i], &tee->slaves[i])) < 0) { |
|
410 |
+ ret = tee_process_slave_failure(avf, i, ret); |
|
411 |
+ if (ret < 0) |
|
412 |
+ goto fail; |
|
413 |
+ } else { |
|
414 |
+ log_slave(&tee->slaves[i], avf, AV_LOG_VERBOSE); |
|
415 |
+ } |
|
412 | 416 |
av_freep(&slaves[i]); |
413 | 417 |
} |
414 | 418 |
|
415 | 419 |
for (i = 0; i < avf->nb_streams; i++) { |
416 | 420 |
int j, mapped = 0; |
417 | 421 |
for (j = 0; j < tee->nb_slaves; j++) |
418 |
- mapped += tee->slaves[j].stream_map[i] >= 0; |
|
422 |
+ if (tee->slaves[j].avf) |
|
423 |
+ mapped += tee->slaves[j].stream_map[i] >= 0; |
|
419 | 424 |
if (!mapped) |
420 | 425 |
av_log(avf, AV_LOG_WARNING, "Input stream #%d is not mapped " |
421 | 426 |
"to any slave.\n", i); |
... | ... |
@@ -436,9 +499,11 @@ static int tee_write_trailer(AVFormatContext *avf) |
436 | 436 |
unsigned i; |
437 | 437 |
|
438 | 438 |
for (i = 0; i < tee->nb_slaves; i++) { |
439 |
- if ((ret = close_slave(&tee->slaves[i])) < 0) |
|
440 |
- if (!ret_all) |
|
439 |
+ if ((ret = close_slave(&tee->slaves[i])) < 0) { |
|
440 |
+ ret = tee_process_slave_failure(avf, i, ret); |
|
441 |
+ if (!ret_all && ret < 0) |
|
441 | 442 |
ret_all = ret; |
443 |
+ } |
|
442 | 444 |
} |
443 | 445 |
return ret_all; |
444 | 446 |
} |
... | ... |
@@ -454,7 +519,9 @@ static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt) |
454 | 454 |
AVRational tb, tb2; |
455 | 455 |
|
456 | 456 |
for (i = 0; i < tee->nb_slaves; i++) { |
457 |
- avf2 = tee->slaves[i].avf; |
|
457 |
+ if (!(avf2 = tee->slaves[i].avf)) |
|
458 |
+ continue; |
|
459 |
+ |
|
458 | 460 |
s = pkt->stream_index; |
459 | 461 |
s2 = tee->slaves[i].stream_map[s]; |
460 | 462 |
if (s2 < 0) |
... | ... |
@@ -475,9 +542,11 @@ static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt) |
475 | 475 |
|
476 | 476 |
if ((ret = av_apply_bitstream_filters(avf2->streams[s2]->codec, &pkt2, |
477 | 477 |
tee->slaves[i].bsfs[s2])) < 0 || |
478 |
- (ret = av_interleaved_write_frame(avf2, &pkt2)) < 0) |
|
479 |
- if (!ret_all) |
|
478 |
+ (ret = av_interleaved_write_frame(avf2, &pkt2)) < 0) { |
|
479 |
+ ret = tee_process_slave_failure(avf, i, ret); |
|
480 |
+ if (!ret_all && ret < 0) |
|
480 | 481 |
ret_all = ret; |
482 |
+ } |
|
481 | 483 |
} |
482 | 484 |
return ret_all; |
483 | 485 |
} |