Compare commits

..

6 Commits

Author SHA1 Message Date
Ninja Jiraiya 7ad49b423e fix for ext-discontinuity 2023-08-21 01:46:33 +08:00
Ninja Jiraiya 5511102b0c try to make kid:key support 2023-07-23 12:15:25 +07:00
Ninja Jiraiya 3ddf77aacd init makefile 2023-07-10 19:00:55 +07:00
Ninja Jiraiya 144e49d077 init smooth streaming 2023-07-10 18:59:42 +07:00
Ninja Jiraiya cf63c0ec19 fix 2023-07-03 07:24:22 +07:00
Ninja Jiraiya 04f4d85e2a fix 2023-07-03 07:21:02 +07:00
7 changed files with 466 additions and 8 deletions

View File

@ -1145,14 +1145,14 @@ static void print_program_info(int flags, int level)
{
const char *indent = flags & INDENT? " " : "";
av_log(NULL, level, "%s version - Customized for MPD-PHP" FFMPEG_VERSION, program_name);
av_log(NULL, level, "%s customized for MPD-PHP version " FFMPEG_VERSION, program_name);
if (flags & SHOW_COPYRIGHT)
av_log(NULL, level, " Copyright (c) %d-%d the FFmpeg developers",
program_birth_year, CONFIG_THIS_YEAR);
av_log(NULL, level, "\n");
av_log(NULL, level, "%sbuilt with %s\n", indent, CC_IDENT);
// av_log(NULL, level, "%sbuilt with %s\n", indent, CC_IDENT);
av_log(NULL, level, "%sconfiguration: " FFMPEG_CONFIGURATION "\n", indent);
// av_log(NULL, level, "%sconfiguration: " FFMPEG_CONFIGURATION "\n", indent);
}
static void print_buildconf(int flags, int level)
@ -1185,7 +1185,6 @@ void show_banner(int argc, char **argv, const OptionDef *options)
{
int idx = locate_option(argc, argv, options, "version");
if (hide_banner || idx)
print_program_info (INDENT|SHOW_COPYRIGHT, AV_LOG_INFO);
return;
print_program_info (INDENT|SHOW_COPYRIGHT, AV_LOG_INFO);

View File

@ -148,6 +148,7 @@ OBJS-$(CONFIG_DATA_DEMUXER) += rawdec.o
OBJS-$(CONFIG_DATA_MUXER) += rawenc.o
OBJS-$(CONFIG_DASH_MUXER) += dash.o dashenc.o hlsplaylist.o
OBJS-$(CONFIG_DASH_DEMUXER) += dash.o dashdec.o
OBJS-$(CONFIG_SMOOTHSTREAMING_DEMUXER) += smoothstreamingdec.o
OBJS-$(CONFIG_DAUD_DEMUXER) += dauddec.o
OBJS-$(CONFIG_DAUD_MUXER) += daudenc.o
OBJS-$(CONFIG_DCSTR_DEMUXER) += dcstr.o

View File

@ -27,6 +27,7 @@
#include "internal.h"
#include "avio_internal.h"
#include "dash.h"
#include "libavformat/isom.h"
#define INITIAL_BUFFER_SIZE 32768
#define MAX_MANIFEST_SIZE 50 * 1024
@ -1878,6 +1879,7 @@ static int reopen_demux_for_component(AVFormatContext *s, struct representation
ff_const59 AVInputFormat *in_fmt = NULL;
AVDictionary *in_fmt_opts = NULL;
uint8_t *avio_ctx_buffer = NULL;
int ret = 0, i;
if (pls->ctx) {
@ -1926,7 +1928,28 @@ static int reopen_demux_for_component(AVFormatContext *s, struct representation
pls->ctx->io_open = nested_io_open;
if (c->cenc_decryption_key)
av_dict_set(&in_fmt_opts, "decryption_key", c->cenc_decryption_key, 0);
// if cenc_decryption_key not contains any : or , it is a hex string
if (strchr(c->cenc_decryption_key, ':') == NULL && strchr(c->cenc_decryption_key, ',') == NULL) {
av_dict_set(&in_fmt_opts, "decryption_key", c->cenc_decryption_key, 0);
} else {
DecryptionKey *decryption_key_map = av_mallocz(sizeof(DecryptionKey *) * 2);
// if cenc_decryption_key contains : or , split it based on ,
char *key_id = av_strtok(c->cenc_decryption_key, ",", &c->cenc_decryption_key);
// if cenc_decryption_key contains : split it based on :
//create DecryptionKey array
*key_count = 0;
while (key_id != NULL && *key_count < 2) {
char* colon = strchr(key_id, ':');
if (colon != NULL) {
*colon = '\0'; // Replace ':' with null-terminator
strncpy(decryption_key_map[*key_count].key_id, key_id, MAX_STRING_LENGTH - 1);
strncpy(decryption_key_map[*key_count].second, colon + 1, MAX_STRING_LENGTH - 1);
(*key_count)++;
}
key_id = av_strtok(NULL, ",");
}
av_dict_set(&in_fmt_opts, "decryption_key_map", decryption_key_map, 0);
}
// provide additional information from mpd if available
ret = avformat_open_input(&pls->ctx, "", in_fmt, &in_fmt_opts); //pls->init_section->url

View File

@ -66,7 +66,9 @@ enum KeyType {
};
struct segment {
int64_t previous_duration;
int64_t duration;
int64_t start_time;
int64_t url_offset;
int64_t size;
char *url;
@ -716,7 +718,7 @@ static int parse_playlist(HLSContext *c, const char *url,
struct playlist *pls, AVIOContext *in)
{
int ret = 0, is_segment = 0, is_variant = 0;
int64_t duration = 0;
int64_t duration = 0, previous_duration1 = 0, previous_duration = 0, total_duration = 0;
enum KeyType key_type = KEY_NONE;
uint8_t iv[16] = "";
int has_iv = 0;
@ -874,6 +876,8 @@ static int parse_playlist(HLSContext *c, const char *url,
} else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
if (pls)
pls->finished = 1;
} else if (av_strstart(line, "#EXT-X-DISCONTINUITY", &ptr)) {
previous_duration = previous_duration1;
} else if (av_strstart(line, "#EXTINF:", &ptr)) {
is_segment = 1;
duration = atof(ptr) * AV_TIME_BASE;
@ -903,6 +907,14 @@ static int parse_playlist(HLSContext *c, const char *url,
ret = AVERROR(ENOMEM);
goto fail;
}
previous_duration1 += duration;
seg->previous_duration = previous_duration;
seg->start_time = total_duration;
total_duration += duration;
seg->duration = duration;
seg->key_type = key_type;
if (has_iv) {
memcpy(seg->iv, iv, sizeof(iv));
} else {
@ -949,8 +961,9 @@ static int parse_playlist(HLSContext *c, const char *url,
" set to default value to 1ms.\n", seg->url);
duration = 0.001 * AV_TIME_BASE;
}
seg->duration = duration;
seg->key_type = key_type;
// seg->duration = duration;
// seg->key_type = key_type;
dynarray_add(&pls->segments, &pls->n_segments, seg);
is_segment = 0;
@ -2257,6 +2270,29 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
ist->time_base,
AV_TIME_BASE_Q);
if (c->playlists[minplaylist]->finished) {
struct playlist *pls = c->playlists[minplaylist];
int seq_no = pls->cur_seq_no - pls->start_seq_no;
if (seq_no < pls->n_segments && s->streams[pkt->stream_index]) {
struct segment *seg = pls->segments[seq_no];
int64_t pred = av_rescale_q(seg->previous_duration,
AV_TIME_BASE_Q,
s->streams[pkt->stream_index]->time_base);
int64_t max_ts = av_rescale_q(seg->start_time + seg->duration,
AV_TIME_BASE_Q,
s->streams[pkt->stream_index]->time_base);
/* EXTINF duration is not precise enough */
max_ts += 2 * AV_TIME_BASE;
if (s->start_time > 0) {
max_ts += av_rescale_q(s->start_time,
AV_TIME_BASE_Q,
s->streams[pkt->stream_index]->time_base);
}
if (pkt->dts != AV_NOPTS_VALUE && pkt->dts + pred < max_ts) pkt->dts += pred;
if (pkt->pts != AV_NOPTS_VALUE && pkt->pts + pred < max_ts) pkt->pts += pred;
}
}
/* There may be more situations where this would be useful, but this at least
* handles newly probed codecs properly (i.e. request_probe by mpegts). */
if (ist->codecpar->codec_id != st->codecpar->codec_id) {

View File

@ -155,6 +155,11 @@ typedef struct MOVIndexRange {
int64_t end;
} MOVIndexRange;
typedef struct DecryptionKey {
uint8_t key_id;
uint8_t decryption_key;
} DecryptionKey;
typedef struct MOVStreamContext {
AVIOContext *pb;
int pb_is_copied;
@ -291,6 +296,8 @@ typedef struct MOVContext {
int decryption_key_len;
int enable_drefs;
int32_t movie_display_matrix[3][3]; ///< display matrix from mvhd
DecryptionKey *decryption_key_map; // array of decryption keys for each track
int decryption_key_map_size;
} MOVContext;
int ff_mp4_read_descr_len(AVIOContext *pb);

View File

@ -6700,6 +6700,62 @@ static int cenc_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *s
return 0;
}
static int cenc_decrypt_with_key(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, uint8_t *decryption_key, int size)
{
int i, ret;
if (sample->scheme != MKBETAG('c','e','n','c') || sample->crypt_byte_block != 0 || sample->skip_byte_block != 0) {
av_log(c->fc, AV_LOG_ERROR, "Only the 'cenc' encryption scheme is supported\n");
return AVERROR_PATCHWELCOME;
}
if (!sc->cenc.aes_ctr) {
/* initialize the cipher */
sc->cenc.aes_ctr = av_aes_ctr_alloc();
if (!sc->cenc.aes_ctr) {
return AVERROR(ENOMEM);
}
ret = av_aes_ctr_init(sc->cenc.aes_ctr, decryption_key);
if (ret < 0) {
return ret;
}
}
av_aes_ctr_set_full_iv(sc->cenc.aes_ctr, sample->iv);
if (!sample->subsample_count)
{
/* decrypt the whole packet */
av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, size);
return 0;
}
for (i = 0; i < sample->subsample_count; i++)
{
if (sample->subsamples[i].bytes_of_clear_data + sample->subsamples[i].bytes_of_protected_data > size) {
av_log(c->fc, AV_LOG_ERROR, "subsample size exceeds the packet size left\n");
return AVERROR_INVALIDDATA;
}
/* skip the clear bytes */
input += sample->subsamples[i].bytes_of_clear_data;
size -= sample->subsamples[i].bytes_of_clear_data;
/* decrypt the encrypted bytes */
av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, sample->subsamples[i].bytes_of_protected_data);
input += sample->subsamples[i].bytes_of_protected_data;
size -= sample->subsamples[i].bytes_of_protected_data;
}
if (size > 0) {
av_log(c->fc, AV_LOG_ERROR, "leftover packet bytes after subsample processing\n");
return AVERROR_INVALIDDATA;
}
return 0;
}
static int cenc_filter(MOVContext *mov, AVStream* st, MOVStreamContext *sc, AVPacket *pkt, int current_index)
{
MOVFragmentStreamInfo *frag_stream_info;
@ -6749,6 +6805,40 @@ static int cenc_filter(MOVContext *mov, AVStream* st, MOVStreamContext *sc, AVPa
if (mov->decryption_key) {
return cenc_decrypt(mov, sc, encrypted_sample, pkt->data, pkt->size);
} else if (mov->decryption_key_map) {
uint8_t *key_id = encrypted_sample->key_id;
uint8_t *dec_key = NULL;
// check size of decryption_key_map size
if (mov->decryption_key_map_size == 1) {
dec_key = mov->decryption_key_map[0].decryption_key;
} else {
// find the decryption key for the given key ID
for (int i = 0; i < mov->decryption_key_map_size; i++) {
// if the key id is equals with the key id in the map
if (key_id == mov->decryption_key_map[i].key_id) {
dec_key = mov->decryption_key_map[i].decryption_key;
break;
}
}
}
if (!dec_key) {
av_log(mov->fc, AV_LOG_ERROR, "No decryption key found for the given key ID\n");
return AVERROR_INVALIDDATA;
}
if (!encrypted_sample->subsample_count) {
// Decrypt the whole packet.
ret = av_aes_ctr_init(&sc->cenc.aes_ctr, dec_key);
if (ret < 0) {
return ret;
}
av_aes_ctr_crypt(&sc->cenc.aes_ctr, pkt->data, pkt->data, pkt->size);
} else {
// Decrypt the subsamples.
ret = cenc_decrypt_with_key(mov, sc, encrypted_sample, pkt->data, dec_key, pkt->size);
if (ret < 0) {
return ret;
}
}
} else {
size_t size;
uint8_t *side_data = av_encryption_info_add_side_data(encrypted_sample, &size);
@ -8219,6 +8309,7 @@ static const AVOption mov_options[] = {
AV_OPT_TYPE_BINARY, {.str="77214d4b196a87cd520045fd20a51d67"},
.flags = AV_OPT_FLAG_DECODING_PARAM },
{ "decryption_key", "The media decryption key (hex)", OFFSET(decryption_key), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM },
{ "decryption_key_map", "The media decryption kid:key (hex) support up to 2", OFFSET(decryption_key_map), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM },
{ "enable_drefs", "Enable external track support.", OFFSET(enable_drefs), AV_OPT_TYPE_BOOL,
{.i64 = 0}, 0, 1, FLAGS },

View File

@ -0,0 +1,301 @@
/*
* 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,
};