typedef struct DASHContext {
const AVClass *class;
char *base_url;
+ char *adaptionset_contenttype_val;
+ char *adaptionset_par_val;
+ char *adaptionset_lang_val;
+ char *adaptionset_minbw_val;
+ char *adaptionset_maxbw_val;
+ char *adaptionset_minwidth_val;
+ char *adaptionset_maxwidth_val;
+ char *adaptionset_minheight_val;
+ char *adaptionset_maxheight_val;
+ char *adaptionset_minframerate_val;
+ char *adaptionset_maxframerate_val;
+ char *adaptionset_segmentalignment_val;
+ char *adaptionset_bitstreamswitching_val;
int n_videos;
struct representation **videos;
uint64_t media_presentation_duration;
uint64_t suggested_presentation_delay;
uint64_t availability_start_time;
+ uint64_t availability_end_time;
uint64_t publish_time;
uint64_t minimum_update_period;
uint64_t time_shift_buffer_depth;
int is_live;
AVIOInterruptCB *interrupt_callback;
- char *user_agent; ///< holds HTTP user agent set as an AVOption to the HTTP protocol context
- char *cookies; ///< holds HTTP cookie values set in either the initial response or as an AVOption to the HTTP protocol context
- char *headers; ///< holds HTTP headers set as an AVOption to the HTTP protocol context
char *allowed_extensions;
AVDictionary *avio_opts;
int max_url_size;
+
+ /* Flags for init section*/
+ int is_init_section_common_video;
+ int is_init_section_common_audio;
+
} DASHContext;
static int ishttp(char *url)
goto finish;
start_time += pls->timelines[i]->duration;
+
+ if (pls->timelines[i]->repeat == -1) {
+ start_time = pls->timelines[i]->duration * cur_seq_no;
+ goto finish;
+ }
+
for (j = 0; j < pls->timelines[i]->repeat; j++) {
num++;
if (num == cur_seq_no)
c->n_audios = 0;
}
-static void set_httpheader_options(DASHContext *c, AVDictionary **opts)
-{
- // broker prior HTTP options that should be consistent across requests
- av_dict_set(opts, "user-agent", c->user_agent, 0);
- av_dict_set(opts, "cookies", c->cookies, 0);
- av_dict_set(opts, "headers", c->headers, 0);
- if (c->is_live) {
- av_dict_set(opts, "seekable", "0", 0);
- }
-}
-static void update_options(char **dest, const char *name, void *src)
-{
- av_freep(dest);
- av_opt_get(src, name, AV_OPT_SEARCH_CHILDREN, (uint8_t**)dest);
- if (*dest)
- av_freep(dest);
-}
-
static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url,
AVDictionary *opts, AVDictionary *opts2, int *is_http)
{
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);
+ "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)) {
av_opt_get(*pb, "cookies", AV_OPT_SEARCH_CHILDREN, (uint8_t**)&new_cookies);
if (new_cookies) {
- av_free(c->cookies);
- c->cookies = new_cookies;
+ av_dict_set(&opts, "cookies", new_cookies, AV_DICT_DONT_STRDUP_VAL);
}
- av_dict_set(&opts, "cookies", c->cookies, 0);
}
av_dict_free(&tmp);
char *baseurl = NULL;
char *root_url = NULL;
char *text = NULL;
+ char *tmp = NULL;
int isRootHttp = 0;
char token ='/';
goto end;
}
av_strlcpy(text, url, strlen(url)+1);
- while (mpdName = av_strtok(text, "/", &text)) {
+ tmp = text;
+ while (mpdName = av_strtok(tmp, "/", &tmp)) {
size = strlen(mpdName);
}
+ av_free(text);
path = av_mallocz(tmp_max_url_size);
tmp_str = av_mallocz(tmp_max_url_size);
if (!(node = baseurl_nodes[rootId])) {
continue;
}
- if (ishttp(xmlNodeGetContent(node))) {
+ text = xmlNodeGetContent(node);
+ if (ishttp(text)) {
+ xmlFree(text);
break;
}
+ xmlFree(text);
}
node = baseurl_nodes[rootId];
}
av_free(path);
av_free(tmp_str);
+ xmlFree(baseurl);
return updated;
}
xmlNodePtr fragment_template_node,
xmlNodePtr content_component_node,
xmlNodePtr adaptionset_baseurl_node,
- xmlNodePtr adaptionset_segmentlist_node)
+ xmlNodePtr adaptionset_segmentlist_node,
+ xmlNodePtr adaptionset_supplementalproperty_node)
{
int32_t ret = 0;
int32_t audio_rep_idx = 0;
char *timescale_val = NULL;
char *initialization_val = NULL;
char *media_val = NULL;
+ char *val = NULL;
xmlNodePtr baseurl_nodes[4];
xmlNodePtr representation_node = node;
char *rep_id_val = xmlGetProp(representation_node, "id");
baseurl_nodes[3] = representation_baseurl_node;
ret = resolve_content_path(s, url, &c->max_url_size, baseurl_nodes, 4);
- c->max_url_size = aligned(c->max_url_size + strlen(rep_id_val) + strlen(rep_bandwidth_val));
+ c->max_url_size = aligned(c->max_url_size
+ + (rep_id_val ? strlen(rep_id_val) : 0)
+ + (rep_bandwidth_val ? strlen(rep_bandwidth_val) : 0));
if (ret == AVERROR(ENOMEM) || ret == 0) {
goto end;
}
rep->first_seq_no = (int64_t) strtoll(startnumber_val, NULL, 10);
xmlFree(startnumber_val);
}
+ if (adaptionset_supplementalproperty_node) {
+ if (!av_strcasecmp(xmlGetProp(adaptionset_supplementalproperty_node,"schemeIdUri"), "http://dashif.org/guidelines/last-segment-number")) {
+ val = xmlGetProp(adaptionset_supplementalproperty_node,"value");
+ if (!val) {
+ av_log(s, AV_LOG_ERROR, "Missing value attribute in adaptionset_supplementalproperty_node\n");
+ } else {
+ rep->last_seq_no =(int64_t) strtoll(val, NULL, 10) - 1;
+ xmlFree(val);
+ }
+ }
+ }
fragment_timeline_node = find_child_node_by_name(representation_segmenttemplate_node, "SegmentTimeline");
xmlNodePtr period_segmentlist_node)
{
int ret = 0;
+ DASHContext *c = s->priv_data;
xmlNodePtr fragment_template_node = NULL;
xmlNodePtr content_component_node = NULL;
xmlNodePtr adaptionset_baseurl_node = NULL;
xmlNodePtr adaptionset_segmentlist_node = NULL;
+ xmlNodePtr adaptionset_supplementalproperty_node = NULL;
xmlNodePtr node = NULL;
+ c->adaptionset_contenttype_val = xmlGetProp(adaptionset_node, "contentType");
+ c->adaptionset_par_val = xmlGetProp(adaptionset_node, "par");
+ c->adaptionset_lang_val = xmlGetProp(adaptionset_node, "lang");
+ c->adaptionset_minbw_val = xmlGetProp(adaptionset_node, "minBandwidth");
+ c->adaptionset_maxbw_val = xmlGetProp(adaptionset_node, "maxBandwidth");
+ c->adaptionset_minwidth_val = xmlGetProp(adaptionset_node, "minWidth");
+ c->adaptionset_maxwidth_val = xmlGetProp(adaptionset_node, "maxWidth");
+ c->adaptionset_minheight_val = xmlGetProp(adaptionset_node, "minHeight");
+ c->adaptionset_maxheight_val = xmlGetProp(adaptionset_node, "maxHeight");
+ c->adaptionset_minframerate_val = xmlGetProp(adaptionset_node, "minFrameRate");
+ c->adaptionset_maxframerate_val = xmlGetProp(adaptionset_node, "maxFrameRate");
+ c->adaptionset_segmentalignment_val = xmlGetProp(adaptionset_node, "segmentAlignment");
+ c->adaptionset_bitstreamswitching_val = xmlGetProp(adaptionset_node, "bitstreamSwitching");
node = xmlFirstElementChild(adaptionset_node);
while (node) {
adaptionset_baseurl_node = node;
} else if (!av_strcasecmp(node->name, (const char *)"SegmentList")) {
adaptionset_segmentlist_node = node;
+ } else if (!av_strcasecmp(node->name, (const char *)"SupplementalProperty")) {
+ adaptionset_supplementalproperty_node = node;
} else if (!av_strcasecmp(node->name, (const char *)"Representation")) {
ret = parse_manifest_representation(s, url, node,
adaptionset_node,
fragment_template_node,
content_component_node,
adaptionset_baseurl_node,
- adaptionset_segmentlist_node);
+ adaptionset_segmentlist_node,
+ adaptionset_supplementalproperty_node);
if (ret < 0) {
return ret;
}
xmlNodePtr root_element = NULL;
xmlNodePtr node = NULL;
xmlNodePtr period_node = NULL;
+ xmlNodePtr tmp_node = NULL;
xmlNodePtr mpd_baseurl_node = NULL;
xmlNodePtr period_baseurl_node = NULL;
xmlNodePtr period_segmenttemplate_node = NULL;
xmlNodePtr adaptionset_node = NULL;
xmlAttrPtr attr = NULL;
char *val = NULL;
- uint32_t perdiod_duration_sec = 0;
- uint32_t perdiod_start_sec = 0;
+ uint32_t period_duration_sec = 0;
+ uint32_t period_start_sec = 0;
if (!in) {
close_in = 1;
- set_httpheader_options(c, &opts);
+ av_dict_copy(&opts, c->avio_opts, 0);
ret = avio_open2(&in, url, AVIO_FLAG_READ, c->interrupt_callback, &opts);
av_dict_free(&opts);
if (ret < 0)
} else {
LIBXML_TEST_VERSION
- doc = xmlReadMemory(buffer, filesize, c->base_url, NULL, 0);
+ doc = xmlReadMemory(buffer, filesize, c->base_url, NULL, 0);
root_element = xmlDocGetRootElement(doc);
node = root_element;
if (!av_strcasecmp(attr->name, (const char *)"availabilityStartTime")) {
c->availability_start_time = get_utc_date_time_insec(s, (const char *)val);
+ } else if (!av_strcasecmp(attr->name, (const char *)"availabilityEndTime")) {
+ c->availability_end_time = get_utc_date_time_insec(s, (const char *)val);
} else if (!av_strcasecmp(attr->name, (const char *)"publishTime")) {
c->publish_time = get_utc_date_time_insec(s, (const char *)val);
} else if (!av_strcasecmp(attr->name, (const char *)"minimumUpdatePeriod")) {
xmlFree(val);
}
- mpd_baseurl_node = find_child_node_by_name(node, "BaseURL");
- if (!mpd_baseurl_node) {
+ tmp_node = find_child_node_by_name(node, "BaseURL");
+ if (tmp_node) {
+ mpd_baseurl_node = xmlCopyNode(tmp_node,1);
+ } else {
mpd_baseurl_node = xmlNewNode(NULL, "BaseURL");
}
node = xmlFirstElementChild(node);
while (node) {
if (!av_strcasecmp(node->name, (const char *)"Period")) {
- perdiod_duration_sec = 0;
- perdiod_start_sec = 0;
+ period_duration_sec = 0;
+ period_start_sec = 0;
attr = node->properties;
while (attr) {
val = xmlGetProp(node, attr->name);
if (!av_strcasecmp(attr->name, (const char *)"duration")) {
- perdiod_duration_sec = get_duration_insec(s, (const char *)val);
+ period_duration_sec = get_duration_insec(s, (const char *)val);
} else if (!av_strcasecmp(attr->name, (const char *)"start")) {
- perdiod_start_sec = get_duration_insec(s, (const char *)val);
+ period_start_sec = get_duration_insec(s, (const char *)val);
}
attr = attr->next;
xmlFree(val);
}
- if ((perdiod_duration_sec) >= (c->period_duration)) {
+ if ((period_duration_sec) >= (c->period_duration)) {
period_node = node;
- c->period_duration = perdiod_duration_sec;
- c->period_start = perdiod_start_sec;
+ c->period_duration = period_duration_sec;
+ c->period_start = period_start_sec;
if (c->period_start > 0)
c->media_presentation_duration = c->period_duration;
}
/*free the document */
xmlFreeDoc(doc);
xmlCleanupParser();
+ xmlFreeNode(mpd_baseurl_node);
}
av_free(new_url);
if (pls->presentation_timeoffset) {
num = pls->presentation_timeoffset * pls->fragment_timescale / pls->fragment_duration;
} else if (c->publish_time > 0 && !c->availability_start_time) {
- num = pls->first_seq_no + (((c->publish_time - c->availability_start_time) - c->suggested_presentation_delay) * pls->fragment_timescale) / pls->fragment_duration;
+ num = pls->first_seq_no + (((c->publish_time - c->time_shift_buffer_depth + pls->fragment_duration) - c->suggested_presentation_delay) * pls->fragment_timescale) / pls->fragment_duration;
} else {
num = pls->first_seq_no + (((get_current_time_in_sec() - c->availability_start_time) - c->suggested_presentation_delay) * pls->fragment_timescale) / pls->fragment_duration;
}
int i = 0;
num = pls->first_seq_no + pls->n_timelines - 1;
for (i = 0; i < pls->n_timelines; i++) {
- num += pls->timelines[i]->repeat;
+ if (pls->timelines[i]->repeat == -1) {
+ int length_of_each_segment = pls->timelines[i]->duration / pls->fragment_timescale;
+ num = c->period_duration / length_of_each_segment;
+ } else {
+ num += pls->timelines[i]->repeat;
+ }
}
} else if (c->is_live && pls->fragment_duration) {
num = pls->first_seq_no + (((get_current_time_in_sec() - c->availability_start_time)) * pls->fragment_timescale) / pls->fragment_duration;
c->videos = NULL;
c->n_audios = 0;
c->audios = NULL;
- ret = parse_manifest(s, s->filename, NULL);
+ ret = parse_manifest(s, s->url, NULL);
if (ret)
goto finish;
if (c->n_videos != n_videos) {
av_log(c, AV_LOG_ERROR,
- "new manifest has mismatched no. of video representations, %d -> %d\n",
- n_videos, c->n_videos);
+ "new manifest has mismatched no. of video representations, %d -> %d\n",
+ n_videos, c->n_videos);
return AVERROR_INVALIDDATA;
}
if (c->n_audios != n_audios) {
av_log(c, AV_LOG_ERROR,
- "new manifest has mismatched no. of audio representations, %d -> %d\n",
- n_audios, c->n_audios);
+ "new manifest has mismatched no. of audio representations, %d -> %d\n",
+ n_audios, c->n_audios);
return AVERROR_INVALIDDATA;
}
return seg;
}
-enum ReadFromURLMode {
- READ_NORMAL,
- READ_COMPLETE,
-};
-
static int read_from_url(struct representation *pls, struct fragment *seg,
- uint8_t *buf, int buf_size,
- enum ReadFromURLMode mode)
+ uint8_t *buf, int buf_size)
{
int ret;
if (seg->size >= 0)
buf_size = FFMIN(buf_size, pls->cur_seg_size - pls->cur_seg_offset);
- if (mode == READ_COMPLETE) {
ret = avio_read(pls->input, buf, buf_size);
- if (ret < buf_size) {
- av_log(pls->parent, AV_LOG_WARNING, "Could not read complete fragment.\n");
- }
- } else {
- ret = avio_read(pls->input, buf, buf_size);
- }
if (ret > 0)
pls->cur_seg_offset += ret;
if (!url) {
goto cleanup;
}
- set_httpheader_options(c, &opts);
+
if (seg->size >= 0) {
/* try to restrict the HTTP request to the part we want
* (if this is in fact a HTTP request) */
av_fast_malloc(&pls->init_sec_buf, &pls->init_sec_buf_size, sec_size);
ret = read_from_url(pls, pls->init_section, pls->init_sec_buf,
- pls->init_sec_buf_size, READ_COMPLETE);
+ pls->init_sec_buf_size);
ff_format_io_close(pls->parent, &pls->input);
if (ret < 0)
ret = AVERROR_EOF;
goto end;
}
- ret = read_from_url(v, v->cur_seg, buf, buf_size, READ_NORMAL);
+ ret = read_from_url(v, v->cur_seg, buf, buf_size);
if (ret > 0)
goto end;
static int save_avio_options(AVFormatContext *s)
{
DASHContext *c = s->priv_data;
- const char *opts[] = { "headers", "user_agent", "user-agent", "cookies", NULL }, **opt = opts;
+ const char *opts[] = { "headers", "user_agent", "cookies", NULL }, **opt = opts;
uint8_t *buf = NULL;
int ret = 0;
av_log(s, AV_LOG_ERROR,
"A DASH playlist item '%s' referred to an external file '%s'. "
"Opening this file was forbidden for security reasons\n",
- s->filename, url);
+ s->url, url);
return AVERROR(EPERM);
}
if (pls->ctx) {
close_demux_for_component(pls);
}
+
+ if (ff_check_interrupt(&s->interrupt_callback)) {
+ ret = AVERROR_EXIT;
+ goto fail;
+ }
+
if (!(pls->ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
goto fail;
pls->parent = s;
pls->cur_seq_no = calc_cur_seg_no(s, pls);
- pls->last_seq_no = calc_max_seg_no(pls, s->priv_data);
+
+ if (!pls->last_seq_no) {
+ pls->last_seq_no = calc_max_seg_no(pls, s->priv_data);
+ }
ret = reopen_demux_for_component(s, pls);
if (ret < 0) {
return ret;
}
+static int is_common_init_section_exist(struct representation **pls, int n_pls)
+{
+ struct fragment *first_init_section = pls[0]->init_section;
+ char *url =NULL;
+ int64_t url_offset = -1;
+ int64_t size = -1;
+ int i = 0;
+
+ if (first_init_section == NULL || n_pls == 0)
+ return 0;
+
+ url = first_init_section->url;
+ url_offset = first_init_section->url_offset;
+ size = pls[0]->init_section->size;
+ for (i=0;i<n_pls;i++) {
+ if (av_strcasecmp(pls[i]->init_section->url,url) || pls[i]->init_section->url_offset != url_offset || pls[i]->init_section->size != size) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void copy_init_section(struct representation *rep_dest, struct representation *rep_src)
+{
+ rep_dest->init_sec_buf = av_mallocz(rep_src->init_sec_buf_size);
+ memcpy(rep_dest->init_sec_buf, rep_src->init_sec_buf, rep_src->init_sec_data_len);
+ rep_dest->init_sec_buf_size = rep_src->init_sec_buf_size;
+ rep_dest->init_sec_data_len = rep_src->init_sec_data_len;
+ rep_dest->cur_timestamp = rep_src->cur_timestamp;
+}
+
+
static int dash_read_header(AVFormatContext *s)
{
- void *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb;
DASHContext *c = s->priv_data;
int ret = 0;
int stream_index = 0;
int i;
c->interrupt_callback = &s->interrupt_callback;
- // if the URL context is good, read important options we must broker later
- if (u) {
- update_options(&c->user_agent, "user-agent", u);
- update_options(&c->cookies, "cookies", u);
- update_options(&c->headers, "headers", u);
- }
- if ((ret = parse_manifest(s, s->filename, s->pb)) < 0)
+ if ((ret = save_avio_options(s)) < 0)
goto fail;
- if ((ret = save_avio_options(s)) < 0)
+ av_dict_set(&c->avio_opts, "seekable", "0", 0);
+
+ if ((ret = parse_manifest(s, s->url, s->pb)) < 0)
goto fail;
/* If this isn't a live stream, fill the total duration of the
s->duration = (int64_t) c->media_presentation_duration * AV_TIME_BASE;
}
+ if(c->n_videos)
+ c->is_init_section_common_video = is_common_init_section_exist(c->videos, c->n_videos);
+
/* Open the demuxer for video and audio components if available */
for (i = 0; i < c->n_videos; i++) {
struct representation *cur_video = c->videos[i];
+ if (i > 0 && c->is_init_section_common_video) {
+ copy_init_section(cur_video,c->videos[0]);
+ }
ret = open_demux_for_component(s, cur_video);
+
if (ret)
goto fail;
cur_video->stream_index = stream_index;
++stream_index;
}
+ if(c->n_audios)
+ c->is_init_section_common_audio = is_common_init_section_exist(c->audios, c->n_audios);
+
for (i = 0; i < c->n_audios; i++) {
struct representation *cur_audio = c->audios[i];
+ if (i > 0 && c->is_init_section_common_audio) {
+ copy_init_section(cur_audio,c->audios[0]);
+ }
ret = open_demux_for_component(s, cur_audio);
+
if (ret)
goto fail;
cur_audio->stream_index = stream_index;
av_dict_set_int(&pls->assoc_stream->metadata, "variant_bitrate", pls->bandwidth, 0);
if (pls->id[0])
av_dict_set(&pls->assoc_stream->metadata, "id", pls->id, 0);
- }
+ }
for (i = 0; i < c->n_audios; i++) {
struct representation *pls = c->audios[i];
free_audio_list(c);
free_video_list(c);
- av_freep(&c->cookies);
- av_freep(&c->user_agent);
av_dict_free(&c->avio_opts);
av_freep(&c->base_url);
return 0;
int64_t duration = 0;
av_log(pls->parent, AV_LOG_VERBOSE, "DASH seek pos[%"PRId64"ms], playlist %d%s\n",
- seek_pos_msec, pls->rep_idx, dry_run ? " (dry)" : "");
+ seek_pos_msec, pls->rep_idx, dry_run ? " (dry)" : "");
// single fragment mode
if (pls->n_fragments == 1) {