FFmpeg4/libavformat/smoothstreamingdec.c

302 lines
9.1 KiB
C

/*
* 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 <libxml/parser.h>
#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,
};