|
|
|
@@ -25,6 +25,7 @@ |
|
|
|
#endif |
|
|
|
|
|
|
|
#include "libavutil/avassert.h" |
|
|
|
#include "libavutil/avutil.h" |
|
|
|
#include "libavutil/avstring.h" |
|
|
|
#include "libavutil/intreadwrite.h" |
|
|
|
#include "libavutil/mathematics.h" |
|
|
|
@@ -50,9 +51,14 @@ typedef struct Segment { |
|
|
|
int n; |
|
|
|
} Segment; |
|
|
|
|
|
|
|
typedef struct AdaptationSet { |
|
|
|
char id[10]; |
|
|
|
enum AVMediaType media_type; |
|
|
|
} AdaptationSet; |
|
|
|
|
|
|
|
typedef struct OutputStream { |
|
|
|
AVFormatContext *ctx; |
|
|
|
int ctx_inited; |
|
|
|
int ctx_inited, as_idx; |
|
|
|
uint8_t iobuf[32768]; |
|
|
|
AVIOContext *out; |
|
|
|
int packets_written; |
|
|
|
@@ -71,6 +77,9 @@ typedef struct OutputStream { |
|
|
|
|
|
|
|
typedef struct DASHContext { |
|
|
|
const AVClass *class; /* Class for private options. */ |
|
|
|
char *adaptation_sets; |
|
|
|
AdaptationSet *as; |
|
|
|
int nb_as; |
|
|
|
int window_size; |
|
|
|
int extra_window_size; |
|
|
|
int min_seg_duration; |
|
|
|
@@ -79,7 +88,7 @@ typedef struct DASHContext { |
|
|
|
int use_timeline; |
|
|
|
int single_file; |
|
|
|
OutputStream *streams; |
|
|
|
int has_video, has_audio; |
|
|
|
int has_video; |
|
|
|
int64_t last_duration; |
|
|
|
int64_t total_duration; |
|
|
|
char availability_start_time[100]; |
|
|
|
@@ -170,6 +179,12 @@ static void dash_free(AVFormatContext *s) |
|
|
|
{ |
|
|
|
DASHContext *c = s->priv_data; |
|
|
|
int i, j; |
|
|
|
|
|
|
|
if (c->as) { |
|
|
|
av_freep(&c->as); |
|
|
|
c->nb_as = 0; |
|
|
|
} |
|
|
|
|
|
|
|
if (!c->streams) |
|
|
|
return; |
|
|
|
for (i = 0; i < s->nb_streams; i++) { |
|
|
|
@@ -317,12 +332,167 @@ static void format_date_now(char *buf, int size) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index) |
|
|
|
{ |
|
|
|
DASHContext *c = s->priv_data; |
|
|
|
AdaptationSet *as = &c->as[as_index]; |
|
|
|
int i; |
|
|
|
|
|
|
|
avio_printf(out, "\t\t<AdaptationSet id=\"%s\" contentType=\"%s\" segmentAlignment=\"true\" bitstreamSwitching=\"true\"", |
|
|
|
as->id, as->media_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio"); |
|
|
|
if (as->media_type == AVMEDIA_TYPE_VIDEO && c->max_frame_rate.num && !c->ambiguous_frame_rate) |
|
|
|
avio_printf(out, " %s=\"%d/%d\"", (av_cmp_q(c->min_frame_rate, c->max_frame_rate) < 0) ? "maxFrameRate" : "frameRate", c->max_frame_rate.num, c->max_frame_rate.den); |
|
|
|
avio_printf(out, ">\n"); |
|
|
|
|
|
|
|
for (i = 0; i < s->nb_streams; i++) { |
|
|
|
OutputStream *os = &c->streams[i]; |
|
|
|
|
|
|
|
if (os->as_idx - 1 != as_index) |
|
|
|
continue; |
|
|
|
|
|
|
|
if (as->media_type == AVMEDIA_TYPE_VIDEO) { |
|
|
|
AVStream *st = s->streams[i]; |
|
|
|
avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\"", |
|
|
|
i, os->codec_str, os->bandwidth_str, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height); |
|
|
|
if (st->avg_frame_rate.num) |
|
|
|
avio_printf(out, " frameRate=\"%d/%d\"", st->avg_frame_rate.num, st->avg_frame_rate.den); |
|
|
|
avio_printf(out, ">\n"); |
|
|
|
} else { |
|
|
|
avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/mp4\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n", |
|
|
|
i, os->codec_str, os->bandwidth_str, s->streams[i]->codecpar->sample_rate); |
|
|
|
avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", |
|
|
|
s->streams[i]->codecpar->channels); |
|
|
|
} |
|
|
|
output_segment_list(os, out, c); |
|
|
|
avio_printf(out, "\t\t\t</Representation>\n"); |
|
|
|
} |
|
|
|
avio_printf(out, "\t\t</AdaptationSet>\n"); |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static int add_adaptation_set(AVFormatContext *s, AdaptationSet **as, enum AVMediaType type) |
|
|
|
{ |
|
|
|
DASHContext *c = s->priv_data; |
|
|
|
|
|
|
|
void *mem = av_realloc(c->as, sizeof(*c->as) * (c->nb_as + 1)); |
|
|
|
if (!mem) |
|
|
|
return AVERROR(ENOMEM); |
|
|
|
c->as = mem; |
|
|
|
++c->nb_as; |
|
|
|
|
|
|
|
*as = &c->as[c->nb_as - 1]; |
|
|
|
memset(*as, 0, sizeof(**as)); |
|
|
|
(*as)->media_type = type; |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static int parse_adaptation_sets(AVFormatContext *s) |
|
|
|
{ |
|
|
|
DASHContext *c = s->priv_data; |
|
|
|
const char *p = c->adaptation_sets; |
|
|
|
enum { new_set, parse_id, parsing_streams } state; |
|
|
|
AdaptationSet *as; |
|
|
|
int i, n, ret; |
|
|
|
enum AVMediaType types[] = { AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_UNKNOWN }; |
|
|
|
|
|
|
|
// default: one AdaptationSet for each media type |
|
|
|
if (!p) { |
|
|
|
for (n = 0; types[n] != AVMEDIA_TYPE_UNKNOWN; n++) { |
|
|
|
int as_idx = 0; |
|
|
|
|
|
|
|
for (i = 0; i < s->nb_streams; i++) { |
|
|
|
if (s->streams[i]->codecpar->codec_type != types[n]) |
|
|
|
continue; |
|
|
|
|
|
|
|
if (!as_idx) { |
|
|
|
if ((ret = add_adaptation_set(s, &as, types[n])) < 0) |
|
|
|
return ret; |
|
|
|
as_idx = c->nb_as; |
|
|
|
|
|
|
|
snprintf(as->id, sizeof(as->id), "%d", i); |
|
|
|
} |
|
|
|
c->streams[i].as_idx = as_idx; |
|
|
|
} |
|
|
|
} |
|
|
|
goto end; |
|
|
|
} |
|
|
|
|
|
|
|
// syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on |
|
|
|
state = new_set; |
|
|
|
while (*p) { |
|
|
|
if (*p == ' ') { |
|
|
|
p++; |
|
|
|
continue; |
|
|
|
} else if (state == new_set && av_strstart(p, "id=", &p)) { |
|
|
|
|
|
|
|
if ((ret = add_adaptation_set(s, &as, AVMEDIA_TYPE_UNKNOWN)) < 0) |
|
|
|
return ret; |
|
|
|
|
|
|
|
n = strcspn(p, ","); |
|
|
|
snprintf(as->id, sizeof(as->id), "%.*s", n, p); |
|
|
|
|
|
|
|
p += n; |
|
|
|
if (*p) |
|
|
|
p++; |
|
|
|
state = parse_id; |
|
|
|
} else if (state == parse_id && av_strstart(p, "streams=", &p)) { |
|
|
|
state = parsing_streams; |
|
|
|
} else if (state == parsing_streams) { |
|
|
|
AdaptationSet *as = &c->as[c->nb_as - 1]; |
|
|
|
OutputStream *os; |
|
|
|
char idx_str[8], *end_str; |
|
|
|
|
|
|
|
n = strcspn(p, " ,"); |
|
|
|
snprintf(idx_str, sizeof(idx_str), "%.*s", n, p); |
|
|
|
p += n; |
|
|
|
|
|
|
|
i = strtol(idx_str, &end_str, 10); |
|
|
|
if (idx_str == end_str || i < 0 || i >= s->nb_streams) { |
|
|
|
av_log(s, AV_LOG_ERROR, "Selected stream \"%s\" not found!\n", idx_str); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
|
|
|
|
os = &c->streams[i]; |
|
|
|
if (as->media_type == AVMEDIA_TYPE_UNKNOWN) { |
|
|
|
as->media_type = s->streams[i]->codecpar->codec_type; |
|
|
|
} else if (as->media_type != s->streams[i]->codecpar->codec_type) { |
|
|
|
av_log(s, AV_LOG_ERROR, "Mixing codec types within an AdaptationSet is not allowed\n"); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} else if (os->as_idx) { |
|
|
|
av_log(s, AV_LOG_ERROR, "Assigning a stream to more than one AdaptationSet is not allowed\n"); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
os->as_idx = c->nb_as; |
|
|
|
|
|
|
|
if (*p == ' ') |
|
|
|
state = new_set; |
|
|
|
if (*p) |
|
|
|
p++; |
|
|
|
} else { |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
end: |
|
|
|
// check for unassigned streams |
|
|
|
for (i = 0; i < s->nb_streams; i++) { |
|
|
|
OutputStream *os = &c->streams[i]; |
|
|
|
if (!os->as_idx) { |
|
|
|
av_log(s, AV_LOG_ERROR, "Stream %d is not mapped to an AdaptationSet\n", i); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
} |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static int write_manifest(AVFormatContext *s, int final) |
|
|
|
{ |
|
|
|
DASHContext *c = s->priv_data; |
|
|
|
AVIOContext *out; |
|
|
|
char temp_filename[1024]; |
|
|
|
int ret, i, as_id = 0; |
|
|
|
int ret, i; |
|
|
|
const char *proto = avio_find_protocol_name(s->filename); |
|
|
|
int use_rename = proto && !strcmp(proto, "file"); |
|
|
|
static unsigned int warned_non_file = 0; |
|
|
|
@@ -393,44 +563,9 @@ static int write_manifest(AVFormatContext *s, int final) |
|
|
|
avio_printf(out, "\t<Period id=\"0\" start=\"PT0.0S\">\n"); |
|
|
|
} |
|
|
|
|
|
|
|
if (c->has_video) { |
|
|
|
avio_printf(out, "\t\t<AdaptationSet id=\"%d\" contentType=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\"", as_id++); |
|
|
|
if (c->max_frame_rate.num && !c->ambiguous_frame_rate) |
|
|
|
avio_printf(out, " %s=\"%d/%d\"", (av_cmp_q(c->min_frame_rate, c->max_frame_rate) < 0) ? "maxFrameRate" : "frameRate", c->max_frame_rate.num, c->max_frame_rate.den); |
|
|
|
avio_printf(out, ">\n"); |
|
|
|
|
|
|
|
for (i = 0; i < s->nb_streams; i++) { |
|
|
|
AVStream *st = s->streams[i]; |
|
|
|
OutputStream *os = &c->streams[i]; |
|
|
|
|
|
|
|
if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) |
|
|
|
continue; |
|
|
|
|
|
|
|
avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\"", i, os->codec_str, os->bandwidth_str, st->codecpar->width, st->codecpar->height); |
|
|
|
if (st->avg_frame_rate.num) |
|
|
|
avio_printf(out, " frameRate=\"%d/%d\"", st->avg_frame_rate.num, st->avg_frame_rate.den); |
|
|
|
avio_printf(out, ">\n"); |
|
|
|
|
|
|
|
output_segment_list(&c->streams[i], out, c); |
|
|
|
avio_printf(out, "\t\t\t</Representation>\n"); |
|
|
|
} |
|
|
|
avio_printf(out, "\t\t</AdaptationSet>\n"); |
|
|
|
} |
|
|
|
if (c->has_audio) { |
|
|
|
avio_printf(out, "\t\t<AdaptationSet id=\"%d\" contentType=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n", as_id++); |
|
|
|
for (i = 0; i < s->nb_streams; i++) { |
|
|
|
AVStream *st = s->streams[i]; |
|
|
|
OutputStream *os = &c->streams[i]; |
|
|
|
|
|
|
|
if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) |
|
|
|
continue; |
|
|
|
|
|
|
|
avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/mp4\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n", i, os->codec_str, os->bandwidth_str, st->codecpar->sample_rate); |
|
|
|
avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st->codecpar->channels); |
|
|
|
output_segment_list(&c->streams[i], out, c); |
|
|
|
avio_printf(out, "\t\t\t</Representation>\n"); |
|
|
|
} |
|
|
|
avio_printf(out, "\t\t</AdaptationSet>\n"); |
|
|
|
for (i = 0; i < c->nb_as; i++) { |
|
|
|
if ((ret = write_adaptation_set(s, out, i)) < 0) |
|
|
|
return ret; |
|
|
|
} |
|
|
|
avio_printf(out, "\t</Period>\n"); |
|
|
|
avio_printf(out, "</MPD>\n"); |
|
|
|
@@ -479,6 +614,9 @@ static int dash_init(AVFormatContext *s) |
|
|
|
if (!c->streams) |
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
if ((ret = parse_adaptation_sets(s)) < 0) |
|
|
|
return ret; |
|
|
|
|
|
|
|
for (i = 0; i < s->nb_streams; i++) { |
|
|
|
OutputStream *os = &c->streams[i]; |
|
|
|
AVFormatContext *ctx; |
|
|
|
@@ -559,8 +697,6 @@ static int dash_init(AVFormatContext *s) |
|
|
|
c->ambiguous_frame_rate = 1; |
|
|
|
} |
|
|
|
c->has_video = 1; |
|
|
|
} else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { |
|
|
|
c->has_audio = 1; |
|
|
|
} |
|
|
|
|
|
|
|
set_codec_str(s, st->codecpar, os->codec_str, sizeof(os->codec_str)); |
|
|
|
@@ -926,6 +1062,7 @@ static int dash_check_bitstream(struct AVFormatContext *s, const AVPacket *avpkt |
|
|
|
#define OFFSET(x) offsetof(DASHContext, x) |
|
|
|
#define E AV_OPT_FLAG_ENCODING_PARAM |
|
|
|
static const AVOption options[] = { |
|
|
|
{ "adaptation_sets", "Adaptation sets. Syntax: id=0,streams=0,1,2 id=1,streams=3,4 and so on", OFFSET(adaptation_sets), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM }, |
|
|
|
{ "window_size", "number of segments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E }, |
|
|
|
{ "extra_window_size", "number of segments kept outside of the manifest before removing from disk", OFFSET(extra_window_size), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, E }, |
|
|
|
{ "min_seg_duration", "minimum segment duration (in microseconds)", OFFSET(min_seg_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E }, |
|
|
|
|