[07/10] lavf: Add an MPEG-DASH ISOFF segmenting muxer

Message ID 1415365019-26521-7-git-send-email-martin@martin.st
State Superseded
Headers show

Commit Message

Martin Storsjö Nov. 7, 2014, 12:56 p.m.
This is mostly to serve as a reference example on how to segment
the output from the mp4 muxer, capable of writing the segment
list in four different ways:
- SegmentTemplate with SegmentTimeline
- SegmentTemplate with implicit segments
- SegmentList with individual files
- SegmentList with one single file per track, and byte ranges

The muxer is able to serve live content (with optional windowing)
or create a static segmented MPD.

In advanced cases, users will probably want to do the segmenting
in their own application code.
---
All uses cases have been tested with dash.js 1.2.0, but the
byte range mode doesn't work as intended there, a fix has been
submitted to the latest git version (but not merged yet).
---
 Changelog                |   2 +-
 configure                |   1 +
 libavformat/Makefile     |   1 +
 libavformat/allformats.c |   1 +
 libavformat/dashenc.c    | 769 +++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/version.h    |   4 +-
 6 files changed, 775 insertions(+), 3 deletions(-)
 create mode 100644 libavformat/dashenc.c

Comments

Martin Storsjö Nov. 7, 2014, 1:38 p.m. | #1
On Fri, 7 Nov 2014, Martin Storsjö wrote:

> This is mostly to serve as a reference example on how to segment
> the output from the mp4 muxer, capable of writing the segment
> list in four different ways:
> - SegmentTemplate with SegmentTimeline
> - SegmentTemplate with implicit segments
> - SegmentList with individual files
> - SegmentList with one single file per track, and byte ranges
>
> The muxer is able to serve live content (with optional windowing)
> or create a static segmented MPD.
>
> In advanced cases, users will probably want to do the segmenting
> in their own application code.
> ---
> All uses cases have been tested with dash.js 1.2.0, but the
> byte range mode doesn't work as intended there, a fix has been
> submitted to the latest git version (but not merged yet).
> ---
> Changelog                |   2 +-
> configure                |   1 +
> libavformat/Makefile     |   1 +
> libavformat/allformats.c |   1 +
> libavformat/dashenc.c    | 769 +++++++++++++++++++++++++++++++++++++++++++++++
> libavformat/version.h    |   4 +-
> 6 files changed, 775 insertions(+), 3 deletions(-)
> create mode 100644 libavformat/dashenc.c

Locally amended to use ff_rename

// Martin
Martin Storsjö Nov. 8, 2014, 8:55 p.m. | #2
On Fri, 7 Nov 2014, Martin Storsjö wrote:

> This is mostly to serve as a reference example on how to segment
> the output from the mp4 muxer, capable of writing the segment
> list in four different ways:
> - SegmentTemplate with SegmentTimeline
> - SegmentTemplate with implicit segments
> - SegmentList with individual files
> - SegmentList with one single file per track, and byte ranges
>
> The muxer is able to serve live content (with optional windowing)
> or create a static segmented MPD.
>
> In advanced cases, users will probably want to do the segmenting
> in their own application code.
> ---
> All uses cases have been tested with dash.js 1.2.0, but the
> byte range mode doesn't work as intended there, a fix has been
> submitted to the latest git version (but not merged yet).
> ---
> Changelog                |   2 +-
> configure                |   1 +
> libavformat/Makefile     |   1 +
> libavformat/allformats.c |   1 +
> libavformat/dashenc.c    | 769 +++++++++++++++++++++++++++++++++++++++++++++++
> libavformat/version.h    |   4 +-
> 6 files changed, 775 insertions(+), 3 deletions(-)
> create mode 100644 libavformat/dashenc.c
>

> +// RFC 6381
> +static void set_codec_str(AVCodecContext *codec, char *str, int size)
> +{
> +    const AVCodecTag *tags[2] = { NULL, NULL };
> +    uint32_t tag;
> +    if (codec->codec_type == AVMEDIA_TYPE_VIDEO)
> +        tags[0] = ff_codec_movvideo_tags;
> +    else if (codec->codec_type == AVMEDIA_TYPE_AUDIO)
> +        tags[0] = ff_codec_movaudio_tags;
> +    else
> +        return;
> +
> +    tag = av_codec_get_tag(tags, codec->codec_id);
> +    if (!tag)
> +        return;
> +    if (size < 5)
> +        return;
> +
> +    AV_WL32(str, tag);
> +    str[4] = '\0';
> +    if (!strcmp(str, "mp4a") || !strcmp(str, "mp4v")) {
> +        tags[0] = ff_mp4_obj_type;
> +        uint32_t oti = av_codec_get_tag(tags, codec->codec_id);
> +        if (oti)
> +            av_strlcatf(str, size, ".%02x", oti);
> +        else
> +            return;
> +
> +        if (tag == MKTAG('m', 'p', '4', 'a')) {
> +            if (codec->extradata_size >= 2) {
> +                int aot = codec->extradata[0] >> 3;
> +                if (aot == 31)
> +                    aot = ((AV_RB16(codec->extradata) >> 5) & 0x3f) + 32;
> +                av_strlcatf(str, size, ".%d", aot);
> +            }
> +        } else if (tag == MKTAG('m', 'p', '4', 'v')) {
> +            // Should output ProfileLevelIndication as a decimal number
> +        }
> +    } else if (!strcmp(str, "avc1")) {
> +        uint8_t *tmpbuf = NULL;
> +        uint8_t *extradata = codec->extradata;
> +        int extradata_size = codec->extradata_size;
> +        if (!extradata_size)
> +            return;
> +        if (extradata[0] != 1) {
> +            AVIOContext *pb;
> +            if (avio_open_dyn_buf(&pb) < 0)
> +                return;
> +            if (ff_isom_write_avcc(pb, extradata, extradata_size) < 0) {
> +                avio_close_dyn_buf(pb, &tmpbuf);
> +                av_free(tmpbuf);
> +                return;
> +            }
> +            extradata_size = avio_close_dyn_buf(pb, &extradata);
> +            tmpbuf = extradata;
> +        }
> +
> +        if (extradata_size >= 4)
> +            av_strlcatf(str, size, ".%02x%02x%02x",
> +                        extradata[1], extradata[2], extradata[3]);
> +        av_free(tmpbuf);
> +    }
> +}

This last part of the function (extracting the h264 
profile/constraint/level bytes) can be simplified quite a bit if we only 
handle the case of extradata in mp4 format, or starting with an annex b 
SPS with a 4 byte startcode, like this:

     } else if (!strcmp(str, "avc1")) {
         const uint8_t *ptr;
         if (codec->extradata_size < 4)
             return;
         if (codec->extradata[0] == 1) {
             ptr = &codec->extradata[1];
         } else if (codec->extradata_size >= 8 &&
                    AV_RB32(codec->extradata) == 1 &&
                    (codec->extradata[4] & 0x1f) == 7) {
             ptr = &codec->extradata[5];
         } else {
             return;
         }

         av_strlcatf(str, size, ".%02x%02x%02x",
                     ptr[0], ptr[1], ptr[2]);
     }

Do people find this better or worse than the version above?

// Martin
Derek Buitenhuis Nov. 14, 2014, 2:57 p.m. | #3
On 11/7/2014 12:56 PM, Martin Storsjö wrote:
> ---
> All uses cases have been tested with dash.js 1.2.0, but the

garbage.js

> diff --git a/Changelog b/Changelog
> index ecec401..6af2e8a 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -6,7 +6,7 @@ version <next>:
>  - HEVC/H.265 RTP payload format (draft v6) packetizer and depacketizer
>  - avplay now exits by default at the end of playback
>  - XCB-based screen-grabber
> -- creating DASH compatible fragmented MP4
> +- creating DASH compatible fragmented MP4, MPEG-DASH segmenting muxer


> +        if (tag == MKTAG('m', 'p', '4', 'a')) {
> +            if (codec->extradata_size >= 2) {
> +                int aot = codec->extradata[0] >> 3;
> +                if (aot == 31)
> +                    aot = ((AV_RB16(codec->extradata) >> 5) & 0x3f) + 32;
> +                av_strlcatf(str, size, ".%d", aot);
> +            }
> +        } else if (tag == MKTAG('m', 'p', '4', 'v')) {
> +            // Should output ProfileLevelIndication as a decimal number

Forgotten impl?

> +        }
> +    } else if (!strcmp(str, "avc1")) {
> +        uint8_t *tmpbuf = NULL;
> +        uint8_t *extradata = codec->extradata;
> +        int extradata_size = codec->extradata_size;
> +        if (!extradata_size)
> +            return;
> +        if (extradata[0] != 1) {
> +            AVIOContext *pb;
> +            if (avio_open_dyn_buf(&pb) < 0)
> +                return;
> +            if (ff_isom_write_avcc(pb, extradata, extradata_size) < 0) {
> +                avio_close_dyn_buf(pb, &tmpbuf);
> +                av_free(tmpbuf);

Won't tmpbuf only ever be NULL here?

> +static char *xmlescape(const char *str) {

Yum yum. I guess we don't have XML code elsewhere to share this with?
(And I know using an external XML lib is a no-no.)

> +    int outlen = strlen(str)*3/2 + 6;

Magic?

> +        if (!c->availability_start_time[0] && s->nb_streams > 0 && c->streams[0].nb_segments > 0) {
> +            time_t t = time(NULL);
> +            struct tm *ptm, tmbuf;
> +            ptm = gmtime_r(&t, &tmbuf);
> +            if (ptm) {
> +                if (!strftime(c->availability_start_time, sizeof(c->availability_start_time),
> +                              "%Y-%m-%dT%H:%M:%S", ptm))
> +                    c->availability_start_time[0] = '\0';
> +            }
> +        }

Don't we have some internal time stuff to use?

> +    oformat = av_guess_format("mp4", NULL, NULL);
> +    if (!oformat) {
> +        ret = AVERROR_MUXER_NOT_FOUND;
> +        goto fail;
> +    }

Isn't this check redundant, as the DASH muxer requires the mp4 muxer?

> +        range_length = avio_tell(os->ctx->pb) - start_pos;
> +        if (c->single_file) {
> +            find_index_range(s, c->dirname, os->initfile, start_pos, &index_length);
> +        } else {
> +            ffurl_close(os->out);
> +            os->out = NULL;
> +            rename(temp_path, full_path);

Do we have an OS override for rename (aka on windows to use _wrename or whatever. I hate Windows multibyte.).

> +            OutputStream *os = &c->streams[i];
> +            snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
> +            unlink(filename);

Ditto.

> +static const AVClass dash_class = {
> +    .class_name = "dash muxer",
> +    .item_name  = av_default_item_name,
> +    .option     = options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +

Extra linebreak.

- Derek
Martin Storsjö Nov. 14, 2014, 3:12 p.m. | #4
On Fri, 14 Nov 2014, Derek Buitenhuis wrote:

> On 11/7/2014 12:56 PM, Martin Storsjö wrote:
>> ---
>> All uses cases have been tested with dash.js 1.2.0, but the
>
> garbage.js
>
>> diff --git a/Changelog b/Changelog
>> index ecec401..6af2e8a 100644
>> --- a/Changelog
>> +++ b/Changelog
>> @@ -6,7 +6,7 @@ version <next>:
>>  - HEVC/H.265 RTP payload format (draft v6) packetizer and depacketizer
>>  - avplay now exits by default at the end of playback
>>  - XCB-based screen-grabber
>> -- creating DASH compatible fragmented MP4
>> +- creating DASH compatible fragmented MP4, MPEG-DASH segmenting muxer
>
>
>> +        if (tag == MKTAG('m', 'p', '4', 'a')) {
>> +            if (codec->extradata_size >= 2) {
>> +                int aot = codec->extradata[0] >> 3;
>> +                if (aot == 31)
>> +                    aot = ((AV_RB16(codec->extradata) >> 5) & 0x3f) + 32;
>> +                av_strlcatf(str, size, ".%d", aot);
>> +            }
>> +        } else if (tag == MKTAG('m', 'p', '4', 'v')) {
>> +            // Should output ProfileLevelIndication as a decimal number
>
> Forgotten impl?

Ah, yes... I had a quick look at how to parse out this from the extradata, 
but figured it seemed too complicated to implement just for completeness. 
Writing mp4v without any profile indication probably is better than 
nothing at all in that case though.

>> +        }
>> +    } else if (!strcmp(str, "avc1")) {
>> +        uint8_t *tmpbuf = NULL;
>> +        uint8_t *extradata = codec->extradata;
>> +        int extradata_size = codec->extradata_size;
>> +        if (!extradata_size)
>> +            return;
>> +        if (extradata[0] != 1) {
>> +            AVIOContext *pb;
>> +            if (avio_open_dyn_buf(&pb) < 0)
>> +                return;
>> +            if (ff_isom_write_avcc(pb, extradata, extradata_size) < 0) {
>> +                avio_close_dyn_buf(pb, &tmpbuf);
>> +                av_free(tmpbuf);
>
> Won't tmpbuf only ever be NULL here?

No, since the allocation of the buffer above succeeded, and 
ff_isom_write_avcc could have succeeded halfways, there still could be 
data in the buffer, that is returned here.

>> +static char *xmlescape(const char *str) {
>
> Yum yum. I guess we don't have XML code elsewhere to share this with?
> (And I know using an external XML lib is a no-no.)
>
>> +    int outlen = strlen(str)*3/2 + 6;
>
> Magic?

Pretty much, aka guess at what will be enough not to need further 
reallocation later on...

>> +        if (!c->availability_start_time[0] && s->nb_streams > 0 && c->streams[0].nb_segments > 0) {
>> +            time_t t = time(NULL);
>> +            struct tm *ptm, tmbuf;
>> +            ptm = gmtime_r(&t, &tmbuf);
>> +            if (ptm) {
>> +                if (!strftime(c->availability_start_time, sizeof(c->availability_start_time),
>> +                              "%Y-%m-%dT%H:%M:%S", ptm))
>> +                    c->availability_start_time[0] = '\0';
>> +            }
>> +        }
>
> Don't we have some internal time stuff to use?

I don't think so, we use strftime at a few other places as well

>> +    oformat = av_guess_format("mp4", NULL, NULL);
>> +    if (!oformat) {
>> +        ret = AVERROR_MUXER_NOT_FOUND;
>> +        goto fail;
>> +    }
>
> Isn't this check redundant, as the DASH muxer requires the mp4 muxer?

Pretty much yes, but I'd rather keep it

>> +        range_length = avio_tell(os->ctx->pb) - start_pos;
>> +        if (c->single_file) {
>> +            find_index_range(s, c->dirname, os->initfile, start_pos, &index_length);
>> +        } else {
>> +            ffurl_close(os->out);
>> +            os->out = NULL;
>> +            rename(temp_path, full_path);
>
> Do we have an OS override for rename (aka on windows to use _wrename or whatever. I hate Windows multibyte.).

We don't, yet, but now with ff_rename we could maybe do that - good idea.

>> +            OutputStream *os = &c->streams[i];
>> +            snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
>> +            unlink(filename);
>
> Ditto.

Ditto

>> +static const AVClass dash_class = {
>> +    .class_name = "dash muxer",
>> +    .item_name  = av_default_item_name,
>> +    .option     = options,
>> +    .version    = LIBAVUTIL_VERSION_INT,
>> +};
>> +
>> +
>
> Extra linebreak.

Will fix

// Martin
Derek Buitenhuis Nov. 14, 2014, 3:16 p.m. | #5
On 11/14/2014 3:12 PM, Martin Storsjö wrote:
>> Forgotten impl?
> 
> Ah, yes... I had a quick look at how to parse out this from the extradata, 
> but figured it seemed too complicated to implement just for completeness. 
> Writing mp4v without any profile indication probably is better than 
> nothing at all in that case though.

Also printing a warning and/or explode.

>> Won't tmpbuf only ever be NULL here?
> 
> No, since the allocation of the buffer above succeeded, and 
> ff_isom_write_avcc could have succeeded halfways, there still could be 
> data in the buffer, that is returned here.

Er, maybe I am blind, but I don't see tmpbuf being allocated?

- Derek
Martin Storsjö Nov. 14, 2014, 3:30 p.m. | #6
On Fri, 14 Nov 2014, Derek Buitenhuis wrote:

> On 11/14/2014 3:12 PM, Martin Storsjö wrote:
>>> Forgotten impl?
>>
>> Ah, yes... I had a quick look at how to parse out this from the extradata,
>> but figured it seemed too complicated to implement just for completeness.
>> Writing mp4v without any profile indication probably is better than
>> nothing at all in that case though.
>
> Also printing a warning and/or explode.

Yeah, a warning might be ok here

>>> Won't tmpbuf only ever be NULL here?
>>
>> No, since the allocation of the buffer above succeeded, and
>> ff_isom_write_avcc could have succeeded halfways, there still could be
>> data in the buffer, that is returned here.
>
> Er, maybe I am blind, but I don't see tmpbuf being allocated?

The semantics of the avio dyn bufs are that you open it with 
avio_open_dyn_buf, which gives you an AVIOContext. All that you write into 
this one is buffered up, and when you close it with avio_close_dyn_buf, it 
deallocs the AVIOContext and hands you the internal buffer, which in this 
case is set in tmpbuf.

// Martin
Derek Buitenhuis Nov. 14, 2014, 3:34 p.m. | #7
On 11/14/2014 3:30 PM, Martin Storsjö wrote:
> The semantics of the avio dyn bufs are that you open it with 
> avio_open_dyn_buf, which gives you an AVIOContext. All that you write into 
> this one is buffered up, and when you close it with avio_close_dyn_buf, it 
> deallocs the AVIOContext and hands you the internal buffer, which in this 
> case is set in tmpbuf.

Hmm OK. That's fairly non-obvious behavior.

- Derek
Martin Storsjö Nov. 14, 2014, 6:38 p.m. | #8
On Fri, 14 Nov 2014, Martin Storsjö wrote:

> On Fri, 14 Nov 2014, Derek Buitenhuis wrote:
>
>> On 11/7/2014 12:56 PM, Martin Storsjö wrote:
>>> ---
>>> +        range_length = avio_tell(os->ctx->pb) - start_pos;
>>> +        if (c->single_file) {
>>> +            find_index_range(s, c->dirname, os->initfile, start_pos, 
>>> &index_length);
>>> +        } else {
>>> +            ffurl_close(os->out);
>>> +            os->out = NULL;
>>> +            rename(temp_path, full_path);
>> 
>> Do we have an OS override for rename (aka on windows to use _wrename or 
>> whatever. I hate Windows multibyte.).
>
> We don't, yet, but now with ff_rename we could maybe do that - good idea.

Actually, we'd need that for all the other existing segmenting muxers as 
well, plus wrapping all of mkdir/rmdir/rename/unlink. I'll make a note to 
try to have a look at that. Are you ok with me deferring that until this 
one has been merged? (The existing hds/smoothstreaming muxers have had 
that issue for ages and nobody have noticed.)

// Martin
Derek Buitenhuis Nov. 14, 2014, 6:50 p.m. | #9
On 11/14/2014 6:38 PM, Martin Storsjö wrote:
> Are you ok with me deferring that until this 
> one has been merged?

Sure.

- Derek

Patch

diff --git a/Changelog b/Changelog
index ecec401..6af2e8a 100644
--- a/Changelog
+++ b/Changelog
@@ -6,7 +6,7 @@  version <next>:
 - HEVC/H.265 RTP payload format (draft v6) packetizer and depacketizer
 - avplay now exits by default at the end of playback
 - XCB-based screen-grabber
-- creating DASH compatible fragmented MP4
+- creating DASH compatible fragmented MP4, MPEG-DASH segmenting muxer
 
 
 version 11:
diff --git a/configure b/configure
index 766f9c2..4b0ef22 100755
--- a/configure
+++ b/configure
@@ -2039,6 +2039,7 @@  avi_muxer_select="riffenc"
 avisynth_demuxer_deps="avisynth"
 avisynth_demuxer_select="riffdec"
 caf_demuxer_select="riffdec"
+dash_muxer_select="mp4_muxer"
 dirac_demuxer_select="dirac_parser"
 dv_demuxer_select="dvprofile"
 dv_muxer_select="dvprofile"
diff --git a/libavformat/Makefile b/libavformat/Makefile
index ff887f0..7ed53a7 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -91,6 +91,7 @@  OBJS-$(CONFIG_CAVSVIDEO_MUXER)           += rawenc.o
 OBJS-$(CONFIG_CDG_DEMUXER)               += cdg.o
 OBJS-$(CONFIG_CDXL_DEMUXER)              += cdxl.o
 OBJS-$(CONFIG_CRC_MUXER)                 += crcenc.o
+OBJS-$(CONFIG_DASH_MUXER)                += dashenc.o isom.o
 OBJS-$(CONFIG_DAUD_DEMUXER)              += dauddec.o
 OBJS-$(CONFIG_DAUD_MUXER)                += daudenc.o
 OBJS-$(CONFIG_DFA_DEMUXER)               += dfa.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index bef155f..7868e3e 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -88,6 +88,7 @@  void av_register_all(void)
     REGISTER_DEMUXER (CDG,              cdg);
     REGISTER_DEMUXER (CDXL,             cdxl);
     REGISTER_MUXER   (CRC,              crc);
+    REGISTER_MUXER   (DASH,             dash);
     REGISTER_MUXDEMUX(DAUD,             daud);
     REGISTER_DEMUXER (DFA,              dfa);
     REGISTER_MUXDEMUX(DIRAC,            dirac);
diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
new file mode 100644
index 0000000..06cd488
--- /dev/null
+++ b/libavformat/dashenc.c
@@ -0,0 +1,769 @@ 
+/*
+ * MPEG-DASH ISO BMFF segmenter
+ * Copyright (c) 2014 Martin Storsjo
+ *
+ * This file is part of Libav.
+ *
+ * Libav 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.
+ *
+ * Libav 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 Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "libavutil/opt.h"
+#include "libavutil/avstring.h"
+#include "libavutil/mathematics.h"
+#include "libavutil/intreadwrite.h"
+
+#include "avc.h"
+#include "avformat.h"
+#include "avio_internal.h"
+#include "internal.h"
+#include "isom.h"
+#include "os_support.h"
+#include "url.h"
+
+typedef struct Segment {
+    char file[1024];
+    int64_t start_pos;
+    int range_length, index_length;
+    int64_t time;
+    int duration;
+    int n;
+} Segment;
+
+typedef struct OutputStream {
+    AVFormatContext *ctx;
+    int ctx_inited;
+    uint8_t iobuf[32768];
+    URLContext *out;
+    int packets_written;
+    char initfile[1024];
+    int64_t init_start_pos;
+    int init_range_length;
+    int nb_segments, segments_size, segment_index;
+    Segment **segments;
+    int64_t first_dts, start_dts, end_dts;
+
+    char codec_str[100];
+} OutputStream;
+
+typedef struct DASHContext {
+    const AVClass *class;  /* Class for private options. */
+    int window_size;
+    int extra_window_size;
+    int min_seg_duration;
+    int remove_at_exit;
+    int use_template;
+    int use_timeline;
+    int single_file;
+    OutputStream *streams;
+    int has_video, has_audio;
+    int nb_segments;
+    int last_duration;
+    int total_duration;
+    char availability_start_time[100];
+    char dirname[1024];
+} DASHContext;
+
+static int dash_write(void *opaque, uint8_t *buf, int buf_size)
+{
+    OutputStream *os = opaque;
+    if (os->out)
+        ffurl_write(os->out, buf, buf_size);
+    return buf_size;
+}
+
+// RFC 6381
+static void set_codec_str(AVCodecContext *codec, char *str, int size)
+{
+    const AVCodecTag *tags[2] = { NULL, NULL };
+    uint32_t tag;
+    if (codec->codec_type == AVMEDIA_TYPE_VIDEO)
+        tags[0] = ff_codec_movvideo_tags;
+    else if (codec->codec_type == AVMEDIA_TYPE_AUDIO)
+        tags[0] = ff_codec_movaudio_tags;
+    else
+        return;
+
+    tag = av_codec_get_tag(tags, codec->codec_id);
+    if (!tag)
+        return;
+    if (size < 5)
+        return;
+
+    AV_WL32(str, tag);
+    str[4] = '\0';
+    if (!strcmp(str, "mp4a") || !strcmp(str, "mp4v")) {
+        tags[0] = ff_mp4_obj_type;
+        uint32_t oti = av_codec_get_tag(tags, codec->codec_id);
+        if (oti)
+            av_strlcatf(str, size, ".%02x", oti);
+        else
+            return;
+
+        if (tag == MKTAG('m', 'p', '4', 'a')) {
+            if (codec->extradata_size >= 2) {
+                int aot = codec->extradata[0] >> 3;
+                if (aot == 31)
+                    aot = ((AV_RB16(codec->extradata) >> 5) & 0x3f) + 32;
+                av_strlcatf(str, size, ".%d", aot);
+            }
+        } else if (tag == MKTAG('m', 'p', '4', 'v')) {
+            // Should output ProfileLevelIndication as a decimal number
+        }
+    } else if (!strcmp(str, "avc1")) {
+        uint8_t *tmpbuf = NULL;
+        uint8_t *extradata = codec->extradata;
+        int extradata_size = codec->extradata_size;
+        if (!extradata_size)
+            return;
+        if (extradata[0] != 1) {
+            AVIOContext *pb;
+            if (avio_open_dyn_buf(&pb) < 0)
+                return;
+            if (ff_isom_write_avcc(pb, extradata, extradata_size) < 0) {
+                avio_close_dyn_buf(pb, &tmpbuf);
+                av_free(tmpbuf);
+                return;
+            }
+            extradata_size = avio_close_dyn_buf(pb, &extradata);
+            tmpbuf = extradata;
+        }
+
+        if (extradata_size >= 4)
+            av_strlcatf(str, size, ".%02x%02x%02x",
+                        extradata[1], extradata[2], extradata[3]);
+        av_free(tmpbuf);
+    }
+}
+
+static void dash_free(AVFormatContext *s)
+{
+    DASHContext *c = s->priv_data;
+    int i, j;
+    if (!c->streams)
+        return;
+    for (i = 0; i < s->nb_streams; i++) {
+        OutputStream *os = &c->streams[i];
+        if (os->ctx && os->ctx_inited)
+            av_write_trailer(os->ctx);
+        if (os->ctx && os->ctx->pb)
+            av_free(os->ctx->pb);
+        ffurl_close(os->out);
+        os->out =  NULL;
+        if (os->ctx)
+            avformat_free_context(os->ctx);
+        for (j = 0; j < os->nb_segments; j++)
+            av_free(os->segments[j]);
+        av_free(os->segments);
+    }
+    av_freep(&c->streams);
+}
+
+static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c)
+{
+    int i, start_index = 0, start_number = 1;
+    if (c->window_size) {
+        start_index  = FFMAX(os->nb_segments   - c->window_size, 0);
+        start_number = FFMAX(os->segment_index - c->window_size, 1);
+    }
+
+    if (c->use_template) {
+        int timescale = c->use_timeline ? os->ctx->streams[0]->time_base.den : AV_TIME_BASE;
+        avio_printf(out, "\t\t\t\t<SegmentTemplate timescale=\"%d\" ", timescale);
+        if (!c->use_timeline)
+            avio_printf(out, "duration=\"%d\" ", c->last_duration);
+        avio_printf(out, "initialization=\"init-stream$RepresentationID$.m4s\" media=\"chunk-stream$RepresentationID$-$Number%%05d$.m4s\" startNumber=\"%d\">\n", c->use_timeline ? start_number : 1);
+        if (c->use_timeline) {
+            avio_printf(out, "\t\t\t\t\t<SegmentTimeline>\n");
+            for (i = start_index; i < os->nb_segments; ) {
+                Segment *seg = os->segments[i];
+                int repeat = 0;
+                avio_printf(out, "\t\t\t\t\t\t<S ");
+                if (i == start_index)
+                    avio_printf(out, "t=\"%"PRId64"\" ", seg->time);
+                avio_printf(out, "d=\"%d\" ", seg->duration);
+                while (i + repeat + 1 < os->nb_segments && os->segments[i + repeat + 1]->duration == seg->duration)
+                    repeat++;
+                if (repeat > 0)
+                    avio_printf(out, "r=\"%d\" ", repeat);
+                avio_printf(out, "/>\n");
+                i += 1 + repeat;
+            }
+            avio_printf(out, "\t\t\t\t\t</SegmentTimeline>\n");
+        }
+        avio_printf(out, "\t\t\t\t</SegmentTemplate>\n");
+    } else if (c->single_file) {
+        avio_printf(out, "\t\t\t\t<BaseURL>%s</BaseURL>\n", os->initfile);
+        avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%d\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number);
+        avio_printf(out, "\t\t\t\t\t<Initialization range=\"%"PRId64"-%"PRId64"\" />\n", os->init_start_pos, os->init_start_pos + os->init_range_length - 1);
+        for (i = start_index; i < os->nb_segments; i++) {
+            Segment *seg = os->segments[i];
+            avio_printf(out, "\t\t\t\t\t<SegmentURL mediaRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->range_length - 1);
+            if (seg->index_length)
+                avio_printf(out, "indexRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->index_length - 1);
+            avio_printf(out, "/>\n");
+        }
+        avio_printf(out, "\t\t\t\t</SegmentList>\n");
+    } else {
+        avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%d\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number);
+        avio_printf(out, "\t\t\t\t\t<Initialization sourceURL=\"%s\" />\n", os->initfile);
+        for (i = start_index; i < os->nb_segments; i++) {
+            Segment *seg = os->segments[i];
+            avio_printf(out, "\t\t\t\t\t<SegmentURL media=\"%s\" />\n", seg->file);
+        }
+        avio_printf(out, "\t\t\t\t</SegmentList>\n");
+    }
+}
+
+static char *xmlescape(const char *str) {
+    int outlen = strlen(str)*3/2 + 6;
+    char *out = av_realloc(NULL, outlen + 1);
+    int pos = 0;
+    if (!out)
+        return NULL;
+    for (; *str; str++) {
+        if (pos + 6 > outlen) {
+            char *tmp;
+            outlen = 2 * outlen + 6;
+            tmp = av_realloc(out, outlen + 1);
+            if (!tmp) {
+                av_free(out);
+                return NULL;
+            }
+            out = tmp;
+        }
+        if (*str == '&') {
+            memcpy(&out[pos], "&amp;", 5);
+            pos += 5;
+        } else if (*str == '<') {
+            memcpy(&out[pos], "&lt;", 4);
+            pos += 4;
+        } else if (*str == '>') {
+            memcpy(&out[pos], "&gt;", 4);
+            pos += 4;
+        } else if (*str == '\'') {
+            memcpy(&out[pos], "&apos;", 6);
+            pos += 6;
+        } else if (*str == '\"') {
+            memcpy(&out[pos], "&quot;", 6);
+            pos += 6;
+        } else {
+            out[pos++] = *str;
+        }
+    }
+    out[pos] = '\0';
+    return out;
+}
+
+static void write_time(AVIOContext *out, int64_t time)
+{
+    int seconds = time / AV_TIME_BASE;
+    int fractions = time % AV_TIME_BASE;
+    int minutes = seconds / 60;
+    int hours = minutes / 60;
+    seconds %= 60;
+    minutes %= 60;
+    avio_printf(out, "PT");
+    if (hours)
+        avio_printf(out, "%dH", hours);
+    if (hours || minutes)
+        avio_printf(out, "%dM", minutes);
+    avio_printf(out, "%d.%dS", seconds, fractions / (AV_TIME_BASE / 10));
+}
+
+static int write_manifest(AVFormatContext *s, int final)
+{
+    DASHContext *c = s->priv_data;
+    AVIOContext *out;
+    char temp_filename[1024];
+    int ret, i;
+    AVDictionaryEntry *title = av_dict_get(s->metadata, "title", NULL, 0);
+
+    snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", s->filename);
+    ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename);
+        return ret;
+    }
+    avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+    avio_printf(out, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+                "\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n"
+                "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
+                "\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n"
+                "\tprofiles=\"urn:mpeg:dash:profile:isoff-live:2011\"\n"
+                "\ttype=\"%s\"\n", final ? "static" : "dynamic");
+    if (final) {
+        avio_printf(out, "\tmediaPresentationDuration=\"");
+        write_time(out, c->total_duration);
+        avio_printf(out, "\"\n");
+    } else {
+        int update_period = c->last_duration / AV_TIME_BASE;
+        if (c->use_template && !c->use_timeline)
+            update_period = 500;
+        avio_printf(out, "\tminimumUpdatePeriod=\"PT%dS\"\n", update_period);
+        avio_printf(out, "\tsuggestedPresentationDelay=\"PT%dS\"\n", c->last_duration / AV_TIME_BASE);
+        if (!c->availability_start_time[0] && s->nb_streams > 0 && c->streams[0].nb_segments > 0) {
+            time_t t = time(NULL);
+            struct tm *ptm, tmbuf;
+            ptm = gmtime_r(&t, &tmbuf);
+            if (ptm) {
+                if (!strftime(c->availability_start_time, sizeof(c->availability_start_time),
+                              "%Y-%m-%dT%H:%M:%S", ptm))
+                    c->availability_start_time[0] = '\0';
+            }
+        }
+        if (c->availability_start_time[0])
+            avio_printf(out, "\tavailabilityStartTime=\"%s\"\n", c->availability_start_time);
+        if (c->window_size && c->use_template) {
+            avio_printf(out, "\ttimeShiftBufferDepth=\"");
+            write_time(out, c->last_duration * c->window_size);
+            avio_printf(out, "\"\n");
+        }
+    }
+    avio_printf(out, "\tminBufferTime=\"");
+    write_time(out, c->last_duration);
+    avio_printf(out, "\">\n");
+    avio_printf(out, "\t<ProgramInformation>\n");
+    if (title) {
+        char *escaped = xmlescape(title->value);
+        avio_printf(out, "\t\t<Title>%s</Title>\n", escaped);
+        av_free(escaped);
+    }
+    avio_printf(out, "\t</ProgramInformation>\n");
+    if (c->window_size && s->nb_streams > 0 && c->streams[0].nb_segments > 0 && !c->use_template) {
+        OutputStream *os = &c->streams[0];
+        int start_index = FFMAX(os->nb_segments - c->window_size, 0);
+        int64_t start_time = av_rescale_q(os->segments[start_index]->time, s->streams[0]->time_base, AV_TIME_BASE_Q);
+        avio_printf(out, "\t<Period start=\"");
+        write_time(out, start_time);
+        avio_printf(out, "\">\n");
+    } else {
+        avio_printf(out, "\t<Period start=\"PT0.0S\">\n");
+    }
+
+    if (c->has_video) {
+        avio_printf(out, "\t\t<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n");
+        for (i = 0; i < s->nb_streams; i++) {
+            AVStream *st = s->streams[i];
+            OutputStream *os = &c->streams[i];
+            if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_VIDEO)
+                continue;
+            avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\" bandwidth=\"%d\" width=\"%d\" height=\"%d\">\n", i, os->codec_str, st->codec->bit_rate, st->codec->width, st->codec->height);
+            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=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n");
+        for (i = 0; i < s->nb_streams; i++) {
+            AVStream *st = s->streams[i];
+            OutputStream *os = &c->streams[i];
+            if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO)
+                continue;
+            avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/mp4\" codecs=\"%s\" bandwidth=\"%d\" audioSamplingRate=\"%d\">\n", i, os->codec_str, st->codec->bit_rate, st->codec->sample_rate);
+            avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st->codec->channels);
+            output_segment_list(&c->streams[i], out, c);
+            avio_printf(out, "\t\t\t</Representation>\n");
+        }
+        avio_printf(out, "\t\t</AdaptationSet>\n");
+    }
+    avio_printf(out, "\t</Period>\n");
+    avio_printf(out, "</MPD>\n");
+    avio_flush(out);
+    avio_close(out);
+    rename(temp_filename, s->filename);
+    return 0;
+}
+
+static int dash_write_header(AVFormatContext *s)
+{
+    DASHContext *c = s->priv_data;
+    int ret = 0, i;
+    AVOutputFormat *oformat;
+    char *ptr;
+    char basename[1024];
+
+    if (c->single_file)
+        c->use_template = 0;
+
+    av_strlcpy(c->dirname, s->filename, sizeof(c->dirname));
+    ptr = strrchr(c->dirname, '/');
+    if (ptr) {
+        av_strlcpy(basename, &ptr[1], sizeof(basename));
+        ptr[1] = '\0';
+    } else {
+        c->dirname[0] = '\0';
+        av_strlcpy(basename, s->filename, sizeof(basename));
+    }
+
+    ptr = strrchr(basename, '.');
+    if (ptr)
+        *ptr = '\0';
+
+    oformat = av_guess_format("mp4", NULL, NULL);
+    if (!oformat) {
+        ret = AVERROR_MUXER_NOT_FOUND;
+        goto fail;
+    }
+
+    c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams);
+    if (!c->streams) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    for (i = 0; i < s->nb_streams; i++) {
+        OutputStream *os = &c->streams[i];
+        AVFormatContext *ctx;
+        AVStream *st;
+        AVDictionary *opts = NULL;
+        char filename[1024];
+
+        if (!s->streams[i]->codec->bit_rate) {
+            av_log(s, AV_LOG_ERROR, "No bit rate set for stream %d\n", i);
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+
+        ctx = avformat_alloc_context();
+        if (!ctx) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        os->ctx = ctx;
+        ctx->oformat = oformat;
+        ctx->interrupt_callback = s->interrupt_callback;
+
+        if (!(st = avformat_new_stream(ctx, NULL))) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        avcodec_copy_context(st->codec, s->streams[i]->codec);
+        st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
+        st->time_base = s->streams[i]->time_base;
+        ctx->avoid_negative_ts = s->avoid_negative_ts;
+
+        ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, dash_write, NULL);
+        if (!ctx->pb) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+
+        if (c->single_file)
+            snprintf(os->initfile, sizeof(os->initfile), "%s-stream%d.m4s", basename, i);
+        else
+            snprintf(os->initfile, sizeof(os->initfile), "init-stream%d.m4s", i);
+        snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
+        ret = ffurl_open(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL);
+        if (ret < 0)
+            goto fail;
+        os->init_start_pos = 0;
+
+        av_dict_set(&opts, "movflags", "frag_custom+dash", 0);
+        if ((ret = avformat_write_header(ctx, &opts)) < 0) {
+             goto fail;
+        }
+        os->ctx_inited = 1;
+        avio_flush(ctx->pb);
+        av_dict_free(&opts);
+
+        if (c->single_file) {
+            os->init_range_length = avio_tell(ctx->pb);
+        } else {
+            ffurl_close(os->out);
+            os->out = NULL;
+        }
+
+        s->streams[i]->time_base = st->time_base;
+        // If the muxer wants to shift timestamps, request to have them shifted
+        // already before being handed to this muxer, so we don't have mismatches
+        // between the MPD and the actual segments.
+        s->avoid_negative_ts = ctx->avoid_negative_ts;
+        if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
+            c->has_video = 1;
+        else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO)
+            c->has_audio = 1;
+
+        set_codec_str(os->ctx->streams[0]->codec, os->codec_str, sizeof(os->codec_str));
+        os->first_dts = AV_NOPTS_VALUE;
+        os->segment_index = 1;
+    }
+
+    if (!c->has_video && c->min_seg_duration <= 0) {
+        av_log(s, AV_LOG_WARNING, "no video stream and no min seg duration set\n");
+        ret = AVERROR(EINVAL);
+    }
+    ret = write_manifest(s, 0);
+
+fail:
+    if (ret)
+        dash_free(s);
+    return ret;
+}
+
+static int add_segment(OutputStream *os, const char *file,
+                       int64_t time, int duration,
+                       int64_t start_pos, int64_t range_length,
+                       int64_t index_length)
+{
+    int err;
+    Segment *seg;
+    if (os->nb_segments >= os->segments_size) {
+        os->segments_size = (os->segments_size + 1) * 2;
+        if ((err = av_reallocp(&os->segments, sizeof(*os->segments) *
+                               os->segments_size)) < 0) {
+            os->segments_size = 0;
+            os->nb_segments = 0;
+            return err;
+        }
+    }
+    seg = av_mallocz(sizeof(*seg));
+    if (!seg)
+        return AVERROR(ENOMEM);
+    av_strlcpy(seg->file, file, sizeof(seg->file));
+    seg->time = time;
+    seg->duration = duration;
+    seg->start_pos = start_pos;
+    seg->range_length = range_length;
+    seg->index_length = index_length;
+    os->segments[os->nb_segments++] = seg;
+    os->segment_index++;
+    return 0;
+}
+
+static void write_styp(AVIOContext *pb)
+{
+    avio_wb32(pb, 24);
+    ffio_wfourcc(pb, "styp");
+    ffio_wfourcc(pb, "msdh");
+    avio_wb32(pb, 0); /* minor */
+    ffio_wfourcc(pb, "msdh");
+    ffio_wfourcc(pb, "msix");
+}
+
+static void find_index_range(AVFormatContext *s, const char *dirname,
+                             const char *filename, int64_t pos,
+                             int *index_length)
+{
+    char full_path[1024];
+    uint8_t buf[8];
+    URLContext *fd;
+    int ret;
+
+    snprintf(full_path, sizeof(full_path), "%s%s", dirname, filename);
+    ret = ffurl_open(&fd, full_path, AVIO_FLAG_READ, &s->interrupt_callback, NULL);
+    if (ret < 0)
+        return;
+    if (ffurl_seek(fd, pos, SEEK_SET) != pos) {
+        ffurl_close(fd);
+        return;
+    }
+    ret = ffurl_read(fd, buf, 8);
+    ffurl_close(fd);
+    if (ret < 8)
+        return;
+    if (AV_RL32(&buf[4]) != MKTAG('s', 'i', 'd', 'x'))
+        return;
+    *index_length = AV_RB32(&buf[0]);
+}
+
+static int dash_flush(AVFormatContext *s, int final)
+{
+    DASHContext *c = s->priv_data;
+    int i, ret = 0;
+
+    for (i = 0; i < s->nb_streams; i++) {
+        OutputStream *os = &c->streams[i];
+        char filename[1024] = "", full_path[1024], temp_path[1024];
+        int64_t start_pos = avio_tell(os->ctx->pb);
+        int range_length, index_length = 0;
+
+        if (!os->packets_written)
+            continue;
+
+        if (!c->single_file) {
+            snprintf(filename, sizeof(filename), "chunk-stream%d-%05d.m4s", i, os->segment_index);
+            snprintf(full_path, sizeof(full_path), "%s%s", c->dirname, filename);
+            snprintf(temp_path, sizeof(temp_path), "%s.tmp", full_path);
+            ret = ffurl_open(&os->out, temp_path, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL);
+            if (ret < 0)
+                break;
+            write_styp(os->ctx->pb);
+        }
+        av_write_frame(os->ctx, NULL);
+        avio_flush(os->ctx->pb);
+        os->packets_written = 0;
+
+        range_length = avio_tell(os->ctx->pb) - start_pos;
+        if (c->single_file) {
+            find_index_range(s, c->dirname, os->initfile, start_pos, &index_length);
+        } else {
+            ffurl_close(os->out);
+            os->out = NULL;
+            rename(temp_path, full_path);
+        }
+        add_segment(os, filename, os->start_dts, os->end_dts - os->start_dts, start_pos, range_length, index_length);
+    }
+
+    if (c->window_size || (final && c->remove_at_exit)) {
+        for (i = 0; i < s->nb_streams; i++) {
+            OutputStream *os = &c->streams[i];
+            int j;
+            int remove = os->nb_segments - c->window_size - c->extra_window_size;
+            if (final && c->remove_at_exit)
+                remove = os->nb_segments;
+            if (remove > 0) {
+                for (j = 0; j < remove; j++) {
+                    char filename[1024];
+                    snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->segments[j]->file);
+                    unlink(filename);
+                    av_free(os->segments[j]);
+                }
+                os->nb_segments -= remove;
+                memmove(os->segments, os->segments + remove, os->nb_segments * sizeof(*os->segments));
+            }
+        }
+    }
+
+    if (ret >= 0)
+        ret = write_manifest(s, final);
+    return ret;
+}
+
+static int dash_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    DASHContext *c = s->priv_data;
+    AVStream *st = s->streams[pkt->stream_index];
+    OutputStream *os = &c->streams[pkt->stream_index];
+    int64_t seg_end_duration = (c->nb_segments + 1) * c->min_seg_duration;
+    int ret;
+
+    // If forcing the stream to start at 0, the mp4 muxer will set the start
+    // timestamps to 0. Do the same here, to avoid mismatches in duration/timestamps.
+    if (os->first_dts == AV_NOPTS_VALUE &&
+        s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO) {
+        pkt->pts -= pkt->dts;
+        pkt->dts  = 0;
+    }
+
+    if (os->first_dts == AV_NOPTS_VALUE)
+        os->first_dts = pkt->dts;
+
+    if ((!c->has_video || st->codec->codec_type == AVMEDIA_TYPE_VIDEO) &&
+        pkt->flags & AV_PKT_FLAG_KEY && os->packets_written &&
+        av_compare_ts(pkt->dts - os->first_dts, st->time_base,
+                      seg_end_duration, AV_TIME_BASE_Q) >= 0) {
+        int64_t prev_duration = c->last_duration;
+
+        c->last_duration = av_rescale_q(pkt->dts - os->start_dts,
+                                        st->time_base,
+                                        AV_TIME_BASE_Q);
+        c->total_duration = av_rescale_q(pkt->dts - os->first_dts,
+                                         st->time_base,
+                                         AV_TIME_BASE_Q);
+
+        if ((!c->use_timeline || !c->use_template) && prev_duration) {
+            if (c->last_duration < prev_duration*9/10 ||
+                c->last_duration > prev_duration*11/10) {
+                av_log(s, AV_LOG_WARNING,
+                       "Segment durations differ too much, enable use_timeline "
+                       "and use_template, or keep a stricter keyframe interval\n");
+            }
+        }
+
+        if ((ret = dash_flush(s, 0)) < 0)
+            return ret;
+        c->nb_segments++;
+    }
+
+    if (!os->packets_written)
+        os->start_dts = pkt->dts;
+    os->end_dts = pkt->dts + pkt->duration;
+    os->packets_written++;
+    return ff_write_chained(os->ctx, 0, pkt, s);
+}
+
+static int dash_write_trailer(AVFormatContext *s)
+{
+    DASHContext *c = s->priv_data;
+
+    if (s->nb_streams > 0) {
+        OutputStream *os = &c->streams[0];
+        // If no segments have been written so far, try to do a crude
+        // guess of the segment duration
+        if (!c->last_duration)
+            c->last_duration = av_rescale_q(os->end_dts - os->start_dts,
+                                            s->streams[0]->time_base,
+                                            AV_TIME_BASE_Q);
+        c->total_duration = av_rescale_q(os->end_dts - os->first_dts,
+                                         s->streams[0]->time_base,
+                                         AV_TIME_BASE_Q);
+    }
+    dash_flush(s, 1);
+
+    if (c->remove_at_exit) {
+        char filename[1024];
+        int i;
+        for (i = 0; i < s->nb_streams; i++) {
+            OutputStream *os = &c->streams[i];
+            snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
+            unlink(filename);
+        }
+        unlink(s->filename);
+    }
+
+    dash_free(s);
+    return 0;
+}
+
+#define OFFSET(x) offsetof(DASHContext, x)
+#define E AV_OPT_FLAG_ENCODING_PARAM
+static const AVOption options[] = {
+    { "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 },
+    { "remove_at_exit", "remove all segments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E },
+    { "use_template", "Use SegmentTemplate instead of SegmentList", OFFSET(use_template), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E },
+    { "use_timeline", "Use SegmentTimeline in SegmentTemplate", OFFSET(use_timeline), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E },
+    { "single_file", "Store all segments in one file, accessed using byte ranges", OFFSET(single_file), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E },
+    { NULL },
+};
+
+static const AVClass dash_class = {
+    .class_name = "dash muxer",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+
+AVOutputFormat ff_dash_muxer = {
+    .name           = "dash",
+    .long_name      = NULL_IF_CONFIG_SMALL("DASH Muxer"),
+    .priv_data_size = sizeof(DASHContext),
+    .audio_codec    = AV_CODEC_ID_AAC,
+    .video_codec    = AV_CODEC_ID_H264,
+    .flags          = AVFMT_GLOBALHEADER | AVFMT_NOFILE | AVFMT_TS_NEGATIVE,
+    .write_header   = dash_write_header,
+    .write_packet   = dash_write_packet,
+    .write_trailer  = dash_write_trailer,
+    .codec_tag      = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
+    .priv_class     = &dash_class,
+};
diff --git a/libavformat/version.h b/libavformat/version.h
index c10a6b8..6692777 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -30,8 +30,8 @@ 
 #include "libavutil/version.h"
 
 #define LIBAVFORMAT_VERSION_MAJOR 56
-#define LIBAVFORMAT_VERSION_MINOR  6
-#define LIBAVFORMAT_VERSION_MICRO  5
+#define LIBAVFORMAT_VERSION_MINOR  7
+#define LIBAVFORMAT_VERSION_MICRO  0
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \