| /* |
| * Tee output protocol |
| * Copyright (c) 2016 Michael Niedermayer |
| * |
| * 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/avstring.h" |
| #include "libavutil/opt.h" |
| #include "avformat.h" |
| #include "avio_internal.h" |
| #include "tee_common.h" |
| |
| typedef struct ChildContext { |
| URLContext *url_context; |
| } ChildContext; |
| |
| typedef struct TeeContext { |
| const AVClass *class; |
| int child_count; |
| ChildContext *child; |
| } TeeContext; |
| |
| static const AVOption tee_options[] = { |
| { NULL } |
| }; |
| |
| static const AVClass tee_class = { |
| .class_name = "tee", |
| .item_name = av_default_item_name, |
| .option = tee_options, |
| .version = LIBAVUTIL_VERSION_INT, |
| }; |
| |
| static const char *const child_delim = "|"; |
| |
| static int tee_write(URLContext *h, const unsigned char *buf, int size) |
| { |
| TeeContext *c = h->priv_data; |
| int i; |
| int main_ret = size; |
| |
| for (i=0; i<c->child_count; i++) { |
| int ret = ffurl_write(c->child[i].url_context, buf, size); |
| if (ret < 0) |
| main_ret = ret; |
| } |
| return main_ret; |
| } |
| |
| static int tee_close(URLContext *h) |
| { |
| TeeContext *c = h->priv_data; |
| int i; |
| int main_ret = 0; |
| |
| for (i=0; i<c->child_count; i++) { |
| int ret = ffurl_closep(&c->child[i].url_context); |
| if (ret < 0) |
| main_ret = ret; |
| } |
| |
| av_freep(&c->child); |
| c->child_count = 0; |
| return main_ret; |
| } |
| |
| static int tee_open(URLContext *h, const char *filename, int flags) |
| { |
| TeeContext *c = h->priv_data; |
| int ret, i; |
| |
| av_strstart(filename, "tee:", &filename); |
| |
| if (flags & AVIO_FLAG_READ) |
| return AVERROR(ENOSYS); |
| |
| while (*filename) { |
| char *child_string = av_get_token(&filename, child_delim); |
| char *child_name = NULL; |
| void *tmp; |
| AVDictionary *options = NULL; |
| if (!child_string) { |
| ret = AVERROR(ENOMEM); |
| goto fail; |
| } |
| |
| tmp = av_realloc_array(c->child, c->child_count + 1, sizeof(*c->child)); |
| if (!tmp) { |
| ret = AVERROR(ENOMEM); |
| goto loop_fail; |
| } |
| c->child = tmp; |
| memset(&c->child[c->child_count], 0, sizeof(c->child[c->child_count])); |
| |
| ret = ff_tee_parse_slave_options(h, child_string, &options, &child_name); |
| if (ret < 0) |
| goto loop_fail; |
| |
| ret = ffurl_open_whitelist(&c->child[c->child_count].url_context, child_name, flags, |
| &h->interrupt_callback, &options, |
| h->protocol_whitelist, h->protocol_blacklist, |
| h); |
| loop_fail: |
| av_freep(&child_string); |
| av_dict_free(&options); |
| if (ret < 0) |
| goto fail; |
| c->child_count++; |
| |
| if (strspn(filename, child_delim)) |
| filename++; |
| } |
| |
| h->is_streamed = 0; |
| for (i=0; i<c->child_count; i++) { |
| h->is_streamed |= c->child[i].url_context->is_streamed; |
| } |
| |
| return 0; |
| fail: |
| tee_close(h); |
| return ret; |
| } |
| const URLProtocol ff_tee_protocol = { |
| .name = "tee", |
| .url_open = tee_open, |
| .url_write = tee_write, |
| .url_close = tee_close, |
| .priv_data_size = sizeof(TeeContext), |
| .priv_data_class = &tee_class, |
| .default_whitelist = "crypto,file,http,https,httpproxy,rtmp,tcp,tls" |
| }; |