/* * HTTP Smooth Streaming based on DASH demuxer * Copyright (c) 2017 samsamsam@o2.pl based on HLS demux * Copyright (c) 2017 Steven Liu * * 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 #include "libavutil/intreadwrite.h" #include "libavutil/opt.h" #include "libavutil/time.h" #include "libavutil/parseutils.h" #include "internal.h" #include "avio_internal.h" #include "dash.h" #define INITIAL_BUFFER_SIZE 32768 #define MAX_MANIFEST_SIZE 50 * 1024 #define DEFAULT_MANIFEST_SIZE 8 * 1024 typedef struct SmoothStreamingContext { const AVClass *class; char *base_url; int is_live; AVIOInterruptCB *interrupt_callback; char *allowed_extensions; AVDictionary *avio_opts; int max_url_size; char *cenc_decryption_key; /* Flags for init section*/ int is_init_section_common_video; int is_init_section_common_audio; } SmoothStreamingContext; static int ishttp(char *url) { const char *proto_name = avio_find_protocol_name(url); return av_strstart(proto_name, "http", NULL); } static int aligned(int val) { return ((val + 0x3F) >> 6) << 6; } static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url, AVDictionary **opts, AVDictionary *opts2, int *is_http) { SmoothStreamingContext *c = s->priv_data; AVDictionary *tmp = NULL; const char *proto_name = NULL; int ret; if (av_strstart(url, "crypto", NULL)) { if (url[6] == '+' || url[6] == ':') proto_name = avio_find_protocol_name(url + 7); } if (!proto_name) proto_name = avio_find_protocol_name(url); if (!proto_name) return AVERROR_INVALIDDATA; // only http(s) & file are allowed if (av_strstart(proto_name, "file", NULL)) { if (strcmp(c->allowed_extensions, "ALL") && !av_match_ext(url, c->allowed_extensions)) { av_log(s, AV_LOG_ERROR, "Filename extension of \'%s\' is not a common multimedia extension, blocked for security reasons.\n" "If you wish to override this adjust allowed_extensions, you can set it to \'ALL\' to allow all\n", url); return AVERROR_INVALIDDATA; } } else if (av_strstart(proto_name, "http", NULL)) { ; } else return AVERROR_INVALIDDATA; if (!strncmp(proto_name, url, strlen(proto_name)) && url[strlen(proto_name)] == ':') ; else if (av_strstart(url, "crypto", NULL) && !strncmp(proto_name, url + 7, strlen(proto_name)) && url[7 + strlen(proto_name)] == ':') ; else if (strcmp(proto_name, "file") || !strncmp(url, "file,", 5)) return AVERROR_INVALIDDATA; av_freep(pb); av_dict_copy(&tmp, *opts, 0); av_dict_copy(&tmp, opts2, 0); ret = avio_open2(pb, url, AVIO_FLAG_READ, c->interrupt_callback, &tmp); if (ret >= 0) { // update cookies on http response with setcookies. char *new_cookies = NULL; if (!(s->flags & AVFMT_FLAG_CUSTOM_IO)) av_opt_get(*pb, "cookies", AV_OPT_SEARCH_CHILDREN, (uint8_t**)&new_cookies); if (new_cookies) { av_dict_set(opts, "cookies", new_cookies, AV_DICT_DONT_STRDUP_VAL); } } av_dict_free(&tmp); if (is_http) *is_http = av_strstart(proto_name, "http", NULL); return ret; } static char *get_content_url(xmlNodePtr *baseurl_nodes, int n_baseurl_nodes, int max_url_size, char *rep_id_val, char *rep_bandwidth_val, char *val) { int i; char *text; char *url = NULL; char *tmp_str = av_mallocz(max_url_size); if (!tmp_str) return NULL; for (i = 0; i < n_baseurl_nodes; ++i) { if (baseurl_nodes[i] && baseurl_nodes[i]->children && baseurl_nodes[i]->children->type == XML_TEXT_NODE) { text = xmlNodeGetContent(baseurl_nodes[i]->children); if (text) { memset(tmp_str, 0, max_url_size); ff_make_absolute_url(tmp_str, max_url_size, "", text); xmlFree(text); } } } if (val) ff_make_absolute_url(tmp_str, max_url_size, tmp_str, val); if (rep_id_val) { url = av_strireplace(tmp_str, "$RepresentationID$", (const char*)rep_id_val); if (!url) { goto end; } av_strlcpy(tmp_str, url, max_url_size); } if (rep_bandwidth_val && tmp_str[0] != '\0') { // free any previously assigned url before reassigning av_free(url); url = av_strireplace(tmp_str, "$Bandwidth$", (const char*)rep_bandwidth_val); if (!url) { goto end; } } end: av_free(tmp_str); return url; } static char *get_val_from_nodes_tab(xmlNodePtr *nodes, const int n_nodes, const char *attrname) { int i; char *val; for (i = 0; i < n_nodes; ++i) { if (nodes[i]) { val = xmlGetProp(nodes[i], attrname); if (val) return val; } } return NULL; } static xmlNodePtr find_child_node_by_name(xmlNodePtr rootnode, const char *nodename) { xmlNodePtr node = rootnode; if (!node) { return NULL; } node = xmlFirstElementChild(node); while (node) { if (!av_strcasecmp(node->name, nodename)) { return node; } node = xmlNextElementSibling(node); } return NULL; } static enum AVMediaType get_content_type(xmlNodePtr node) { enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN; int i = 0; const char *attr; char *val = NULL; if (node) { for (i = 0; i < 2; i++) { attr = i ? "mimeType" : "contentType"; val = xmlGetProp(node, attr); if (val) { if (av_stristr((const char *)val, "video")) { type = AVMEDIA_TYPE_VIDEO; } else if (av_stristr((const char *)val, "audio")) { type = AVMEDIA_TYPE_AUDIO; } else if (av_stristr((const char *)val, "text")) { type = AVMEDIA_TYPE_SUBTITLE; } xmlFree(val); } } } return type; } static int save_avio_options(AVFormatContext *s) { SmoothStreamingContext *c = s->priv_data; const char *opts[] = { "headers", "user_agent", "cookies", "http_proxy", "referer", "rw_timeout", "icy", NULL }; const char **opt = opts; uint8_t *buf = NULL; int ret = 0; while (*opt) { if (av_opt_get(s->pb, *opt, AV_OPT_SEARCH_CHILDREN, &buf) >= 0) { if (buf[0] != '\0') { ret = av_dict_set(&c->avio_opts, *opt, buf, AV_DICT_DONT_STRDUP_VAL); if (ret < 0) return ret; } else { av_freep(&buf); } } opt++; } return ret; } static int nested_io_open(AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **opts) { av_log(s, AV_LOG_ERROR, "A SmoothStreaming playlist item '%s' referred to an external file '%s'. " "Opening this file was forbidden for security reasons\n", s->url, url); return AVERROR(EPERM); } #define OFFSET(x) offsetof(DASHContext, x) #define FLAGS AV_OPT_FLAG_DECODING_PARAM static const AVOption dash_options[] = { {"allowed_extensions", "List of file extensions that dash is allowed to access", OFFSET(allowed_extensions), AV_OPT_TYPE_STRING, {.str = "aac,m4a,m4s,m4v,mov,mp4,webm,ts"}, INT_MIN, INT_MAX, FLAGS}, { "cenc_decryption_key", "Media decryption key (hex)", OFFSET(cenc_decryption_key), AV_OPT_TYPE_STRING, {.str = NULL}, INT_MIN, INT_MAX, .flags = FLAGS }, {NULL} }; static const AVClass dash_class = { .class_name = "dash", .item_name = av_default_item_name, .option = dash_options, .version = LIBAVUTIL_VERSION_INT, }; AVInputFormat ff_dash_demuxer = { .name = "dash", .long_name = NULL_IF_CONFIG_SMALL("Dynamic Adaptive Streaming over HTTP"), .priv_class = &dash_class, .priv_data_size = sizeof(DASHContext), .read_probe = dash_probe, .read_header = dash_read_header, .read_packet = dash_read_packet, .read_close = dash_close, .read_seek = dash_read_seek, .flags = AVFMT_NO_BYTE_SEEK, };