| 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 tee_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 = tee_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 | ✗ | tee_slave->on_fail = DEFAULT_SLAVE_FAILURE_POLICY; | |
| 171 | |||
| 172 | #define CONSUME_OPTION(option, field, action) do { \ | ||
| 173 | AVDictionaryEntry *en = av_dict_get(options, option, NULL, 0); \ | ||
| 174 | if (en) { \ | ||
| 175 | field = en->value; \ | ||
| 176 | { action } \ | ||
| 177 | av_dict_set(&options, option, NULL, 0); \ | ||
| 178 | } \ | ||
| 179 | } while (0) | ||
| 180 | #define STEAL_OPTION(option, field) \ | ||
| 181 | CONSUME_OPTION(option, field, \ | ||
| 182 | en->value = NULL; /* prevent it from being freed */) | ||
| 183 | #define PROCESS_OPTION(option, function, on_error) do { \ | ||
| 184 | const char *value; \ | ||
| 185 | CONSUME_OPTION(option, value, if ((ret = function) < 0) \ | ||
| 186 | { { on_error } goto end; }); \ | ||
| 187 | } while (0) | ||
| 188 | |||
| 189 | ✗ | STEAL_OPTION("f", format); | |
| 190 | ✗ | STEAL_OPTION("select", select); | |
| 191 | ✗ | PROCESS_OPTION("onfail", | |
| 192 | parse_slave_failure_policy_option(value, tee_slave), | ||
| 193 | av_log(avf, AV_LOG_ERROR, "Invalid onfail option value, " | ||
| 194 | "valid options are 'abort' and 'ignore'\n");); | ||
| 195 | ✗ | PROCESS_OPTION("use_fifo", | |
| 196 | parse_slave_fifo_policy(value, tee_slave), | ||
| 197 | av_log(avf, AV_LOG_ERROR, "Error parsing fifo options: %s\n", | ||
| 198 | av_err2str(ret));); | ||
| 199 | ✗ | PROCESS_OPTION("fifo_options", | |
| 200 | parse_slave_fifo_options(value, tee_slave), ;); | ||
| 201 | ✗ | entry = NULL; | |
| 202 | ✗ | while ((entry = av_dict_get(options, "bsfs", NULL, AV_DICT_IGNORE_SUFFIX))) { | |
| 203 | /* trim out strlen("bsfs") characters from key */ | ||
| 204 | ✗ | av_dict_set(&bsf_options, entry->key + 4, entry->value, 0); | |
| 205 | ✗ | av_dict_set(&options, entry->key, NULL, 0); | |
| 206 | } | ||
| 207 | |||
| 208 | ✗ | if (tee_slave->use_fifo) { | |
| 209 | |||
| 210 | ✗ | if (options) { | |
| 211 | ✗ | char *format_options_str = NULL; | |
| 212 | ✗ | ret = av_dict_get_string(options, &format_options_str, '=', ':'); | |
| 213 | ✗ | if (ret < 0) | |
| 214 | ✗ | goto end; | |
| 215 | |||
| 216 | ✗ | ret = av_dict_set(&tee_slave->fifo_options, "format_opts", format_options_str, | |
| 217 | AV_DICT_DONT_STRDUP_VAL); | ||
| 218 | ✗ | if (ret < 0) | |
| 219 | ✗ | goto end; | |
| 220 | } | ||
| 221 | |||
| 222 | ✗ | if (format) { | |
| 223 | ✗ | ret = av_dict_set(&tee_slave->fifo_options, "fifo_format", format, | |
| 224 | AV_DICT_DONT_STRDUP_VAL); | ||
| 225 | ✗ | format = NULL; | |
| 226 | ✗ | if (ret < 0) | |
| 227 | ✗ | goto end; | |
| 228 | } | ||
| 229 | |||
| 230 | ✗ | av_dict_free(&options); | |
| 231 | ✗ | options = tee_slave->fifo_options; | |
| 232 | ✗ | tee_slave->fifo_options = NULL; | |
| 233 | } | ||
| 234 | ✗ | ret = avformat_alloc_output_context2(&avf2, NULL, | |
| 235 | ✗ | tee_slave->use_fifo ? "fifo" :format, filename); | |
| 236 | ✗ | if (ret < 0) | |
| 237 | ✗ | goto end; | |
| 238 | ✗ | tee_slave->avf = avf2; | |
| 239 | ✗ | av_dict_copy(&avf2->metadata, avf->metadata, 0); | |
| 240 | ✗ | avf2->opaque = avf->opaque; | |
| 241 | ✗ | avf2->io_open = avf->io_open; | |
| 242 | ✗ | avf2->io_close2 = avf->io_close2; | |
| 243 | ✗ | avf2->interrupt_callback = avf->interrupt_callback; | |
| 244 | ✗ | avf2->flags = avf->flags; | |
| 245 | ✗ | avf2->strict_std_compliance = avf->strict_std_compliance; | |
| 246 | |||
| 247 | ✗ | tee_slave->stream_map = av_calloc(avf->nb_streams, sizeof(*tee_slave->stream_map)); | |
| 248 | ✗ | if (!tee_slave->stream_map) { | |
| 249 | ✗ | ret = AVERROR(ENOMEM); | |
| 250 | ✗ | goto end; | |
| 251 | } | ||
| 252 | |||
| 253 | ✗ | stream_count = 0; | |
| 254 | ✗ | for (unsigned i = 0; i < avf->nb_streams; i++) { | |
| 255 | ✗ | const AVStream *st = avf->streams[i]; | |
| 256 | AVStream *st2; | ||
| 257 | ✗ | if (select) { | |
| 258 | ✗ | tmp_select = av_strdup(select); // av_strtok is destructive so we regenerate it in each loop | |
| 259 | ✗ | if (!tmp_select) { | |
| 260 | ✗ | ret = AVERROR(ENOMEM); | |
| 261 | ✗ | goto end; | |
| 262 | } | ||
| 263 | ✗ | fullret = 0; | |
| 264 | ✗ | first_subselect = tmp_select; | |
| 265 | ✗ | next_subselect = NULL; | |
| 266 | ✗ | while (subselect = av_strtok(first_subselect, slave_select_sep, &next_subselect)) { | |
| 267 | ✗ | first_subselect = NULL; | |
| 268 | |||
| 269 | ✗ | ret = avformat_match_stream_specifier(avf, avf->streams[i], subselect); | |
| 270 | ✗ | if (ret < 0) { | |
| 271 | ✗ | av_log(avf, AV_LOG_ERROR, | |
| 272 | "Invalid stream specifier '%s' for output '%s'\n", | ||
| 273 | subselect, slave); | ||
| 274 | ✗ | goto end; | |
| 275 | } | ||
| 276 | ✗ | if (ret != 0) { | |
| 277 | ✗ | fullret = 1; // match | |
| 278 | ✗ | break; | |
| 279 | } | ||
| 280 | } | ||
| 281 | ✗ | av_freep(&tmp_select); | |
| 282 | |||
| 283 | ✗ | if (fullret == 0) { /* no match */ | |
| 284 | ✗ | tee_slave->stream_map[i] = -1; | |
| 285 | ✗ | continue; | |
| 286 | } | ||
| 287 | } | ||
| 288 | ✗ | tee_slave->stream_map[i] = stream_count++; | |
| 289 | |||
| 290 | ✗ | st2 = ff_stream_clone(avf2, st); | |
| 291 | ✗ | if (!st2) { | |
| 292 | ✗ | ret = AVERROR(ENOMEM); | |
| 293 | ✗ | goto end; | |
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | ✗ | ret = ff_format_output_open(avf2, filename, &options); | |
| 298 | ✗ | if (ret < 0) { | |
| 299 | ✗ | av_log(avf, AV_LOG_ERROR, "Slave '%s': error opening: %s\n", slave, | |
| 300 | ✗ | av_err2str(ret)); | |
| 301 | ✗ | goto end; | |
| 302 | } | ||
| 303 | |||
| 304 | ✗ | if ((ret = avformat_write_header(avf2, &options)) < 0) { | |
| 305 | ✗ | av_log(avf, AV_LOG_ERROR, "Slave '%s': error writing header: %s\n", | |
| 306 | ✗ | slave, av_err2str(ret)); | |
| 307 | ✗ | goto end; | |
| 308 | } | ||
| 309 | ✗ | tee_slave->header_written = 1; | |
| 310 | |||
| 311 | ✗ | tee_slave->bsfs = av_calloc(avf2->nb_streams, sizeof(*tee_slave->bsfs)); | |
| 312 | ✗ | if (!tee_slave->bsfs) { | |
| 313 | ✗ | ret = AVERROR(ENOMEM); | |
| 314 | ✗ | goto end; | |
| 315 | } | ||
| 316 | |||
| 317 | ✗ | entry = NULL; | |
| 318 | ✗ | while (entry = av_dict_iterate(bsf_options, NULL)) { | |
| 319 | ✗ | const char *spec = entry->key; | |
| 320 | ✗ | if (*spec) { | |
| 321 | ✗ | if (strspn(spec, slave_bsfs_spec_sep) != 1) { | |
| 322 | ✗ | av_log(avf, AV_LOG_ERROR, | |
| 323 | "Specifier separator in '%s' is '%c', but only characters '%s' " | ||
| 324 | ✗ | "are allowed\n", entry->key, *spec, slave_bsfs_spec_sep); | |
| 325 | ✗ | ret = AVERROR(EINVAL); | |
| 326 | ✗ | goto end; | |
| 327 | } | ||
| 328 | ✗ | spec++; /* consume separator */ | |
| 329 | } | ||
| 330 | |||
| 331 | ✗ | for (unsigned i = 0; i < avf2->nb_streams; i++) { | |
| 332 | ✗ | ret = avformat_match_stream_specifier(avf2, avf2->streams[i], spec); | |
| 333 | ✗ | if (ret < 0) { | |
| 334 | ✗ | av_log(avf, AV_LOG_ERROR, | |
| 335 | "Invalid stream specifier '%s' in bsfs option '%s' for slave " | ||
| 336 | ✗ | "output '%s'\n", spec, entry->key, filename); | |
| 337 | ✗ | goto end; | |
| 338 | } | ||
| 339 | |||
| 340 | ✗ | if (ret > 0) { | |
| 341 | ✗ | av_log(avf, AV_LOG_DEBUG, "spec:%s bsfs:%s matches stream %d of slave " | |
| 342 | ✗ | "output '%s'\n", spec, entry->value, i, filename); | |
| 343 | ✗ | if (tee_slave->bsfs[i]) { | |
| 344 | ✗ | av_log(avf, AV_LOG_WARNING, | |
| 345 | "Duplicate bsfs specification associated to stream %d of slave " | ||
| 346 | "output '%s', filters will be ignored\n", i, filename); | ||
| 347 | ✗ | continue; | |
| 348 | } | ||
| 349 | ✗ | ret = av_bsf_list_parse_str(entry->value, &tee_slave->bsfs[i]); | |
| 350 | ✗ | if (ret < 0) { | |
| 351 | ✗ | av_log(avf, AV_LOG_ERROR, | |
| 352 | "Error parsing bitstream filter sequence '%s' associated to " | ||
| 353 | ✗ | "stream %d of slave output '%s'\n", entry->value, i, filename); | |
| 354 | ✗ | goto end; | |
| 355 | } | ||
| 356 | } | ||
| 357 | } | ||
| 358 | |||
| 359 | ✗ | av_dict_set(&bsf_options, entry->key, NULL, 0); | |
| 360 | } | ||
| 361 | |||
| 362 | ✗ | for (unsigned i = 0; i < avf->nb_streams; i++){ | |
| 363 | ✗ | int target_stream = tee_slave->stream_map[i]; | |
| 364 | ✗ | if (target_stream < 0) | |
| 365 | ✗ | continue; | |
| 366 | |||
| 367 | ✗ | if (!tee_slave->bsfs[target_stream]) { | |
| 368 | /* Add pass-through bitstream filter */ | ||
| 369 | ✗ | ret = av_bsf_get_null_filter(&tee_slave->bsfs[target_stream]); | |
| 370 | ✗ | if (ret < 0) { | |
| 371 | ✗ | av_log(avf, AV_LOG_ERROR, | |
| 372 | "Failed to create pass-through bitstream filter: %s\n", | ||
| 373 | ✗ | av_err2str(ret)); | |
| 374 | ✗ | goto end; | |
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | ✗ | tee_slave->bsfs[target_stream]->time_base_in = avf->streams[i]->time_base; | |
| 379 | ✗ | ret = avcodec_parameters_copy(tee_slave->bsfs[target_stream]->par_in, | |
| 380 | ✗ | avf->streams[i]->codecpar); | |
| 381 | ✗ | if (ret < 0) | |
| 382 | ✗ | goto end; | |
| 383 | |||
| 384 | ✗ | ret = av_bsf_init(tee_slave->bsfs[target_stream]); | |
| 385 | ✗ | if (ret < 0) { | |
| 386 | ✗ | av_log(avf, AV_LOG_ERROR, | |
| 387 | "Failed to initialize bitstream filter(s): %s\n", | ||
| 388 | ✗ | av_err2str(ret)); | |
| 389 | ✗ | goto end; | |
| 390 | } | ||
| 391 | } | ||
| 392 | |||
| 393 | ✗ | if (options) { | |
| 394 | ✗ | entry = NULL; | |
| 395 | ✗ | while ((entry = av_dict_iterate(options, entry))) | |
| 396 | ✗ | av_log(avf2, AV_LOG_ERROR, "Unknown option '%s'\n", entry->key); | |
| 397 | ✗ | ret = AVERROR_OPTION_NOT_FOUND; | |
| 398 | ✗ | goto end; | |
| 399 | } | ||
| 400 | |||
| 401 | ✗ | end: | |
| 402 | ✗ | av_free(format); | |
| 403 | ✗ | av_free(select); | |
| 404 | ✗ | av_dict_free(&options); | |
| 405 | ✗ | av_dict_free(&bsf_options); | |
| 406 | ✗ | av_freep(&tmp_select); | |
| 407 | ✗ | return ret; | |
| 408 | } | ||
| 409 | |||
| 410 | ✗ | static void log_slave(TeeSlave *slave, void *log_ctx, int log_level) | |
| 411 | { | ||
| 412 | ✗ | av_log(log_ctx, log_level, "filename:'%s' format:%s\n", | |
| 413 | ✗ | slave->avf->url, slave->avf->oformat->name); | |
| 414 | ✗ | for (unsigned i = 0; i < slave->avf->nb_streams; i++) { | |
| 415 | ✗ | AVStream *st = slave->avf->streams[i]; | |
| 416 | ✗ | AVBSFContext *bsf = slave->bsfs[i]; | |
| 417 | const char *bsf_name; | ||
| 418 | |||
| 419 | ✗ | av_log(log_ctx, log_level, " stream:%d codec:%s type:%s", | |
| 420 | ✗ | i, avcodec_get_name(st->codecpar->codec_id), | |
| 421 | ✗ | av_get_media_type_string(st->codecpar->codec_type)); | |
| 422 | |||
| 423 | ✗ | bsf_name = bsf->filter->priv_class ? | |
| 424 | ✗ | bsf->filter->priv_class->item_name(bsf) : bsf->filter->name; | |
| 425 | ✗ | av_log(log_ctx, log_level, " bsfs: %s\n", bsf_name); | |
| 426 | } | ||
| 427 | ✗ | } | |
| 428 | |||
| 429 | ✗ | static int tee_process_slave_failure(AVFormatContext *avf, unsigned slave_idx, int err_n) | |
| 430 | { | ||
| 431 | ✗ | TeeContext *tee = avf->priv_data; | |
| 432 | ✗ | TeeSlave *tee_slave = &tee->slaves[slave_idx]; | |
| 433 | |||
| 434 | ✗ | tee->nb_alive--; | |
| 435 | |||
| 436 | ✗ | close_slave(tee_slave); | |
| 437 | |||
| 438 | ✗ | if (!tee->nb_alive) { | |
| 439 | ✗ | av_log(avf, AV_LOG_ERROR, "All tee outputs failed.\n"); | |
| 440 | ✗ | return err_n; | |
| 441 | ✗ | } else if (tee_slave->on_fail == ON_SLAVE_FAILURE_ABORT) { | |
| 442 | ✗ | av_log(avf, AV_LOG_ERROR, "Slave muxer #%u failed, aborting.\n", slave_idx); | |
| 443 | ✗ | return err_n; | |
| 444 | } else { | ||
| 445 | ✗ | av_log(avf, AV_LOG_ERROR, "Slave muxer #%u failed: %s, continuing with %u/%u slaves.\n", | |
| 446 | ✗ | slave_idx, av_err2str(err_n), tee->nb_alive, tee->nb_slaves); | |
| 447 | ✗ | return 0; | |
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | ✗ | static int tee_write_header(AVFormatContext *avf) | |
| 452 | { | ||
| 453 | ✗ | TeeContext *tee = avf->priv_data; | |
| 454 | ✗ | unsigned nb_slaves = 0; | |
| 455 | ✗ | const char *filename = avf->url; | |
| 456 | ✗ | char **slaves = NULL; | |
| 457 | int ret; | ||
| 458 | |||
| 459 | ✗ | while (*filename) { | |
| 460 | ✗ | char *slave = av_get_token(&filename, slave_delim); | |
| 461 | ✗ | if (!slave) { | |
| 462 | ✗ | ret = AVERROR(ENOMEM); | |
| 463 | ✗ | goto fail; | |
| 464 | } | ||
| 465 | ✗ | ret = av_dynarray_add_nofree(&slaves, &nb_slaves, slave); | |
| 466 | ✗ | if (ret < 0) { | |
| 467 | ✗ | av_free(slave); | |
| 468 | ✗ | goto fail; | |
| 469 | } | ||
| 470 | ✗ | if (strspn(filename, slave_delim)) | |
| 471 | ✗ | filename++; | |
| 472 | } | ||
| 473 | |||
| 474 | ✗ | if (!FF_ALLOCZ_TYPED_ARRAY(tee->slaves, nb_slaves)) { | |
| 475 | ✗ | ret = AVERROR(ENOMEM); | |
| 476 | ✗ | goto fail; | |
| 477 | } | ||
| 478 | ✗ | tee->nb_slaves = tee->nb_alive = nb_slaves; | |
| 479 | |||
| 480 | ✗ | for (unsigned i = 0; i < nb_slaves; i++) { | |
| 481 | |||
| 482 | ✗ | tee->slaves[i].use_fifo = tee->use_fifo; | |
| 483 | ✗ | ret = av_dict_copy(&tee->slaves[i].fifo_options, tee->fifo_options, 0); | |
| 484 | ✗ | if (ret < 0) | |
| 485 | ✗ | goto fail; | |
| 486 | |||
| 487 | ✗ | if ((ret = open_slave(avf, slaves[i], &tee->slaves[i])) < 0) { | |
| 488 | ✗ | ret = tee_process_slave_failure(avf, i, ret); | |
| 489 | ✗ | if (ret < 0) | |
| 490 | ✗ | goto fail; | |
| 491 | } else { | ||
| 492 | ✗ | log_slave(&tee->slaves[i], avf, AV_LOG_VERBOSE); | |
| 493 | } | ||
| 494 | ✗ | av_freep(&slaves[i]); | |
| 495 | } | ||
| 496 | |||
| 497 | ✗ | for (unsigned i = 0; i < avf->nb_streams; i++) { | |
| 498 | ✗ | int mapped = 0; | |
| 499 | ✗ | for (unsigned j = 0; j < tee->nb_slaves; j++) | |
| 500 | ✗ | if (tee->slaves[j].avf) | |
| 501 | ✗ | mapped += tee->slaves[j].stream_map[i] >= 0; | |
| 502 | ✗ | if (!mapped) | |
| 503 | ✗ | av_log(avf, AV_LOG_WARNING, "Input stream #%d is not mapped " | |
| 504 | "to any slave.\n", i); | ||
| 505 | } | ||
| 506 | ✗ | av_free(slaves); | |
| 507 | ✗ | return 0; | |
| 508 | |||
| 509 | ✗ | fail: | |
| 510 | ✗ | for (unsigned i = 0; i < nb_slaves; i++) | |
| 511 | ✗ | av_freep(&slaves[i]); | |
| 512 | ✗ | close_slaves(avf); | |
| 513 | ✗ | av_free(slaves); | |
| 514 | ✗ | return ret; | |
| 515 | } | ||
| 516 | |||
| 517 | ✗ | static int tee_write_trailer(AVFormatContext *avf) | |
| 518 | { | ||
| 519 | ✗ | TeeContext *tee = avf->priv_data; | |
| 520 | ✗ | int ret_all = 0, ret; | |
| 521 | |||
| 522 | ✗ | for (unsigned i = 0; i < tee->nb_slaves; i++) { | |
| 523 | ✗ | if ((ret = close_slave(&tee->slaves[i])) < 0) { | |
| 524 | ✗ | ret = tee_process_slave_failure(avf, i, ret); | |
| 525 | ✗ | if (!ret_all && ret < 0) | |
| 526 | ✗ | ret_all = ret; | |
| 527 | } | ||
| 528 | } | ||
| 529 | ✗ | av_freep(&tee->slaves); | |
| 530 | ✗ | return ret_all; | |
| 531 | } | ||
| 532 | |||
| 533 | ✗ | static int tee_write_packet(AVFormatContext *avf, AVPacket *pkt) | |
| 534 | { | ||
| 535 | ✗ | TeeContext *tee = avf->priv_data; | |
| 536 | ✗ | AVPacket *const pkt2 = ffformatcontext(avf)->pkt; | |
| 537 | ✗ | int ret_all = 0, ret; | |
| 538 | unsigned s; | ||
| 539 | int s2; | ||
| 540 | |||
| 541 | ✗ | for (unsigned i = 0; i < tee->nb_slaves; i++) { | |
| 542 | ✗ | AVFormatContext *avf2 = tee->slaves[i].avf; | |
| 543 | AVBSFContext *bsfs; | ||
| 544 | |||
| 545 | ✗ | if (!avf2) | |
| 546 | ✗ | continue; | |
| 547 | |||
| 548 | /* Flush slave if pkt is NULL*/ | ||
| 549 | ✗ | if (!pkt) { | |
| 550 | ✗ | ret = av_interleaved_write_frame(avf2, NULL); | |
| 551 | ✗ | if (ret < 0) { | |
| 552 | ✗ | ret = tee_process_slave_failure(avf, i, ret); | |
| 553 | ✗ | if (!ret_all && ret < 0) | |
| 554 | ✗ | ret_all = ret; | |
| 555 | } | ||
| 556 | ✗ | continue; | |
| 557 | } | ||
| 558 | |||
| 559 | ✗ | s = pkt->stream_index; | |
| 560 | ✗ | s2 = tee->slaves[i].stream_map[s]; | |
| 561 | ✗ | if (s2 < 0) | |
| 562 | ✗ | continue; | |
| 563 | |||
| 564 | ✗ | if ((ret = av_packet_ref(pkt2, pkt)) < 0) { | |
| 565 | ✗ | if (!ret_all) | |
| 566 | ✗ | ret_all = ret; | |
| 567 | ✗ | continue; | |
| 568 | } | ||
| 569 | ✗ | bsfs = tee->slaves[i].bsfs[s2]; | |
| 570 | ✗ | pkt2->stream_index = s2; | |
| 571 | |||
| 572 | ✗ | ret = av_bsf_send_packet(bsfs, pkt2); | |
| 573 | ✗ | if (ret < 0) { | |
| 574 | ✗ | av_packet_unref(pkt2); | |
| 575 | ✗ | av_log(avf, AV_LOG_ERROR, "Error while sending packet to bitstream filter: %s\n", | |
| 576 | ✗ | av_err2str(ret)); | |
| 577 | ✗ | ret = tee_process_slave_failure(avf, i, ret); | |
| 578 | ✗ | if (!ret_all && ret < 0) | |
| 579 | ✗ | ret_all = ret; | |
| 580 | } | ||
| 581 | |||
| 582 | while(1) { | ||
| 583 | ✗ | ret = av_bsf_receive_packet(bsfs, pkt2); | |
| 584 | ✗ | if (ret == AVERROR(EAGAIN)) { | |
| 585 | ✗ | ret = 0; | |
| 586 | ✗ | break; | |
| 587 | ✗ | } else if (ret < 0) { | |
| 588 | ✗ | break; | |
| 589 | } | ||
| 590 | |||
| 591 | ✗ | av_packet_rescale_ts(pkt2, bsfs->time_base_out, | |
| 592 | ✗ | avf2->streams[s2]->time_base); | |
| 593 | ✗ | ret = av_interleaved_write_frame(avf2, pkt2); | |
| 594 | ✗ | if (ret < 0) | |
| 595 | ✗ | break; | |
| 596 | }; | ||
| 597 | |||
| 598 | ✗ | if (ret < 0) { | |
| 599 | ✗ | ret = tee_process_slave_failure(avf, i, ret); | |
| 600 | ✗ | if (!ret_all && ret < 0) | |
| 601 | ✗ | ret_all = ret; | |
| 602 | } | ||
| 603 | } | ||
| 604 | ✗ | return ret_all; | |
| 605 | } | ||
| 606 | |||
| 607 | const FFOutputFormat ff_tee_muxer = { | ||
| 608 | .p.name = "tee", | ||
| 609 | .p.long_name = NULL_IF_CONFIG_SMALL("Multiple muxer tee"), | ||
| 610 | .priv_data_size = sizeof(TeeContext), | ||
| 611 | .write_header = tee_write_header, | ||
| 612 | .write_trailer = tee_write_trailer, | ||
| 613 | .write_packet = tee_write_packet, | ||
| 614 | .p.priv_class = &tee_muxer_class, | ||
| 615 | .p.flags = AVFMT_NOFILE | AVFMT_TS_NEGATIVE, | ||
| 616 | .flags_internal = FF_OFMT_FLAG_ALLOW_FLUSH, | ||
| 617 | }; | ||
| 618 |