Browse code

Add a smooth streaming segmenter muxer

This muxer splits the output from the ismv muxer into individual
files, in realtime.

The same can also be done by the standalone tool ismindex, but this
muxer is needed for doing it in realtime (especially for live
streams that need extra handling for updating the lookahead fields
in the fragment headers).

Using this muxer, one can deliver live smooth streaming from a
normal static file web server. (Using ismindex, one can deliver
premade smooth streaming files from a static file web server,
or prepare files for serving with IIS.)

Signed-off-by: Martin Storsjö <martin@martin.st>

Martin Storsjö authored on 2012/03/20 06:12:54
Showing 6 changed files
... ...
@@ -47,6 +47,7 @@ version <next>:
47 47
 - Ut Video encoder
48 48
 - Microsoft Screen 2 decoder
49 49
 - RTP depacketization of JPEG
50
+- Smooth Streaming live segmenter muxer
50 51
 
51 52
 
52 53
 version 0.8:
... ...
@@ -292,6 +292,7 @@ OBJS-$(CONFIG_SIFF_DEMUXER)              += siff.o
292 292
 OBJS-$(CONFIG_SMACKER_DEMUXER)           += smacker.o
293 293
 OBJS-$(CONFIG_SMJPEG_DEMUXER)            += smjpegdec.o smjpeg.o
294 294
 OBJS-$(CONFIG_SMJPEG_MUXER)              += smjpegenc.o smjpeg.o
295
+OBJS-$(CONFIG_SMOOTHSTREAMING_MUXER)     += smoothstreamingenc.o
295 296
 OBJS-$(CONFIG_SOL_DEMUXER)               += sol.o pcm.o
296 297
 OBJS-$(CONFIG_SOX_DEMUXER)               += soxdec.o pcm.o
297 298
 OBJS-$(CONFIG_SOX_MUXER)                 += soxenc.o
... ...
@@ -207,6 +207,7 @@ void av_register_all(void)
207 207
     REGISTER_DEMUXER  (SIFF, siff);
208 208
     REGISTER_DEMUXER  (SMACKER, smacker);
209 209
     REGISTER_MUXDEMUX (SMJPEG, smjpeg);
210
+    REGISTER_MUXER    (SMOOTHSTREAMING, smoothstreaming);
210 211
     REGISTER_DEMUXER  (SOL, sol);
211 212
     REGISTER_MUXDEMUX (SOX, sox);
212 213
     REGISTER_MUXDEMUX (SPDIF, spdif);
... ...
@@ -38,6 +38,13 @@
38 38
 #  define fstat(f,s) _fstati64((f), (s))
39 39
 #endif /* defined(__MINGW32__) && !defined(__MINGW32CE__) */
40 40
 
41
+#ifdef _WIN32
42
+#include <direct.h>
43
+#define mkdir(a, b) _mkdir(a)
44
+#else
45
+#include <sys/stat.h>
46
+#endif
47
+
41 48
 static inline int is_dos_path(const char *path)
42 49
 {
43 50
 #if HAVE_DOS_PATHS
44 51
new file mode 100644
... ...
@@ -0,0 +1,621 @@
0
+/*
1
+ * Live smooth streaming fragmenter
2
+ * Copyright (c) 2012 Martin Storsjo
3
+ *
4
+ * This file is part of Libav.
5
+ *
6
+ * Libav is free software; you can redistribute it and/or
7
+ * modify it under the terms of the GNU Lesser General Public
8
+ * License as published by the Free Software Foundation; either
9
+ * version 2.1 of the License, or (at your option) any later version.
10
+ *
11
+ * Libav is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
+ * Lesser General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU Lesser General Public
17
+ * License along with Libav; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
+ */
20
+
21
+#include "config.h"
22
+#include <float.h>
23
+#if HAVE_UNISTD_H
24
+#include <unistd.h>
25
+#endif
26
+
27
+#include "avformat.h"
28
+#include "internal.h"
29
+#include "os_support.h"
30
+#include "avc.h"
31
+#include "url.h"
32
+
33
+#include "libavutil/opt.h"
34
+#include "libavutil/avstring.h"
35
+#include "libavutil/mathematics.h"
36
+#include "libavutil/intreadwrite.h"
37
+
38
+typedef struct {
39
+    char file[1024];
40
+    char infofile[1024];
41
+    int64_t start_time, duration;
42
+    int n;
43
+    int64_t start_pos, size;
44
+} Fragment;
45
+
46
+typedef struct {
47
+    AVFormatContext *ctx;
48
+    int ctx_inited;
49
+    char dirname[1024];
50
+    uint8_t iobuf[32768];
51
+    URLContext *out;  // Current output stream where all output is written
52
+    URLContext *out2; // Auxillary output stream where all output also is written
53
+    URLContext *tail_out; // The actual main output stream, if we're currently seeked back to write elsewhere
54
+    int64_t tail_pos, cur_pos, cur_start_pos;
55
+    int packets_written;
56
+    const char *stream_type_tag;
57
+    int nb_fragments, fragments_size, fragment_index;
58
+    Fragment **fragments;
59
+
60
+    const char *fourcc;
61
+    char *private_str;
62
+    int packet_size;
63
+    int audio_tag;
64
+} OutputStream;
65
+
66
+typedef struct {
67
+    const AVClass *class;  /* Class for private options. */
68
+    int window_size;
69
+    int extra_window_size;
70
+    int lookahead_count;
71
+    int min_frag_duration;
72
+    int remove_at_exit;
73
+    OutputStream *streams;
74
+    int has_video, has_audio;
75
+    int nb_fragments;
76
+} SmoothStreamingContext;
77
+
78
+static int ism_write(void *opaque, uint8_t *buf, int buf_size)
79
+{
80
+    OutputStream *os = opaque;
81
+    if (os->out)
82
+        ffurl_write(os->out, buf, buf_size);
83
+    if (os->out2)
84
+        ffurl_write(os->out2, buf, buf_size);
85
+    os->cur_pos += buf_size;
86
+    if (os->cur_pos >= os->tail_pos)
87
+        os->tail_pos = os->cur_pos;
88
+    return buf_size;
89
+}
90
+
91
+static int64_t ism_seek(void *opaque, int64_t offset, int whence)
92
+{
93
+    OutputStream *os = opaque;
94
+    int i;
95
+    if (whence != SEEK_SET)
96
+        return AVERROR(ENOSYS);
97
+    if (os->tail_out) {
98
+        if (os->out) {
99
+            ffurl_close(os->out);
100
+        }
101
+        if (os->out2) {
102
+            ffurl_close(os->out2);
103
+        }
104
+        os->out = os->tail_out;
105
+        os->out2 = NULL;
106
+        os->tail_out = NULL;
107
+    }
108
+    if (offset >= os->cur_start_pos) {
109
+        ffurl_seek(os->out, offset - os->cur_start_pos, SEEK_SET);
110
+        os->cur_pos = offset;
111
+        return offset;
112
+    }
113
+    for (i = os->nb_fragments - 1; i >= 0; i--) {
114
+        Fragment *frag = os->fragments[i];
115
+        if (offset >= frag->start_pos && offset < frag->start_pos + frag->size) {
116
+            int ret;
117
+            AVDictionary *opts = NULL;
118
+            os->tail_out = os->out;
119
+            av_dict_set(&opts, "truncate", "0", 0);
120
+            ret = ffurl_open(&os->out, frag->file, AVIO_FLAG_READ_WRITE, &os->ctx->interrupt_callback, &opts);
121
+            av_dict_free(&opts);
122
+            if (ret < 0) {
123
+                os->out = os->tail_out;
124
+                os->tail_out = NULL;
125
+                return ret;
126
+            }
127
+            av_dict_set(&opts, "truncate", "0", 0);
128
+            ffurl_open(&os->out2, frag->infofile, AVIO_FLAG_READ_WRITE, &os->ctx->interrupt_callback, &opts);
129
+            av_dict_free(&opts);
130
+            ffurl_seek(os->out, offset - frag->start_pos, SEEK_SET);
131
+            if (os->out2)
132
+                ffurl_seek(os->out2, offset - frag->start_pos, SEEK_SET);
133
+            os->cur_pos = offset;
134
+            return offset;
135
+        }
136
+    }
137
+    return AVERROR(EIO);
138
+}
139
+
140
+static void get_private_data(OutputStream *os)
141
+{
142
+    AVCodecContext *codec = os->ctx->streams[0]->codec;
143
+    uint8_t *ptr = codec->extradata;
144
+    int size = codec->extradata_size;
145
+    int i;
146
+    if (codec->codec_id == AV_CODEC_ID_H264) {
147
+        ff_avc_write_annexb_extradata(ptr, &ptr, &size);
148
+        if (!ptr)
149
+            ptr = codec->extradata;
150
+    }
151
+    if (!ptr)
152
+        return;
153
+    os->private_str = av_mallocz(2*size + 1);
154
+    for (i = 0; i < size; i++)
155
+        snprintf(&os->private_str[2*i], 3, "%02x", ptr[i]);
156
+    if (ptr != codec->extradata)
157
+        av_free(ptr);
158
+}
159
+
160
+static void ism_free(AVFormatContext *s)
161
+{
162
+    SmoothStreamingContext *c = s->priv_data;
163
+    int i, j;
164
+    if (!c->streams)
165
+        return;
166
+    for (i = 0; i < s->nb_streams; i++) {
167
+        OutputStream *os = &c->streams[i];
168
+        ffurl_close(os->out);
169
+        ffurl_close(os->out2);
170
+        ffurl_close(os->tail_out);
171
+        os->out = os->out2 = os->tail_out = NULL;
172
+        if (os->ctx && os->ctx_inited)
173
+            av_write_trailer(os->ctx);
174
+        if (os->ctx && os->ctx->pb)
175
+            av_free(os->ctx->pb);
176
+        if (os->ctx)
177
+            avformat_free_context(os->ctx);
178
+        av_free(os->private_str);
179
+        for (j = 0; j < os->nb_fragments; j++)
180
+            av_free(os->fragments[j]);
181
+        av_free(os->fragments);
182
+    }
183
+    av_freep(&c->streams);
184
+}
185
+
186
+static int ism_write_header(AVFormatContext *s)
187
+{
188
+    SmoothStreamingContext *c = s->priv_data;
189
+    int ret = 0, i;
190
+    AVOutputFormat *oformat;
191
+
192
+    ret = mkdir(s->filename, 0777);
193
+    if (ret) {
194
+        av_log(s, AV_LOG_ERROR, "mkdir(%s): %s\n", s->filename, strerror(errno));
195
+        return AVERROR(errno);
196
+    }
197
+    ret = 0;
198
+
199
+    oformat = av_guess_format("ismv", NULL, NULL);
200
+    if (!oformat) {
201
+        ret = AVERROR_MUXER_NOT_FOUND;
202
+        goto fail;
203
+    }
204
+
205
+    c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams);
206
+    if (!c->streams) {
207
+        ret = AVERROR(ENOMEM);
208
+        goto fail;
209
+    }
210
+
211
+    for (i = 0; i < s->nb_streams; i++) {
212
+        OutputStream *os = &c->streams[i];
213
+        AVFormatContext *ctx;
214
+        AVStream *st;
215
+        AVDictionary *opts = NULL;
216
+        char buf[10];
217
+
218
+        if (!s->streams[i]->codec->bit_rate) {
219
+            av_log(s, AV_LOG_ERROR, "No bit rate set for stream %d\n", i);
220
+            ret = AVERROR(EINVAL);
221
+            goto fail;
222
+        }
223
+        snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(%d)", s->filename, s->streams[i]->codec->bit_rate);
224
+        mkdir(os->dirname, 0777);
225
+
226
+        ctx = avformat_alloc_context();
227
+        if (!ctx) {
228
+            ret = AVERROR(ENOMEM);
229
+            goto fail;
230
+        }
231
+        os->ctx = ctx;
232
+        ctx->oformat = oformat;
233
+        ctx->interrupt_callback = s->interrupt_callback;
234
+
235
+        if (!(st = avformat_new_stream(ctx, NULL))) {
236
+            ret = AVERROR(ENOMEM);
237
+            goto fail;
238
+        }
239
+        avcodec_copy_context(st->codec, s->streams[i]->codec);
240
+
241
+        ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, ism_write, ism_seek);
242
+        if (!ctx->pb) {
243
+            ret = AVERROR(ENOMEM);
244
+            goto fail;
245
+        }
246
+
247
+        snprintf(buf, sizeof(buf), "%d", c->lookahead_count);
248
+        av_dict_set(&opts, "ism_lookahead", buf, 0);
249
+        av_dict_set(&opts, "movflags", "frag_custom", 0);
250
+        if ((ret = avformat_write_header(ctx, &opts)) < 0) {
251
+             goto fail;
252
+        }
253
+        os->ctx_inited = 1;
254
+        avio_flush(ctx->pb);
255
+        av_dict_free(&opts);
256
+        s->streams[i]->time_base = st->time_base;
257
+        if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
258
+            c->has_video = 1;
259
+            os->stream_type_tag = "video";
260
+            if (st->codec->codec_id == AV_CODEC_ID_H264) {
261
+                os->fourcc = "H264";
262
+            } else if (st->codec->codec_id == AV_CODEC_ID_VC1) {
263
+                os->fourcc = "WVC1";
264
+            } else {
265
+                av_log(s, AV_LOG_ERROR, "Unsupported video codec\n");
266
+                ret = AVERROR(EINVAL);
267
+                goto fail;
268
+            }
269
+        } else {
270
+            c->has_audio = 1;
271
+            os->stream_type_tag = "audio";
272
+            if (st->codec->codec_id == AV_CODEC_ID_AAC) {
273
+                os->fourcc = "AACL";
274
+                os->audio_tag = 0xff;
275
+            } else if (st->codec->codec_id == AV_CODEC_ID_WMAPRO) {
276
+                os->fourcc = "WMAP";
277
+                os->audio_tag = 0x0162;
278
+            } else {
279
+                av_log(s, AV_LOG_ERROR, "Unsupported audio codec\n");
280
+                ret = AVERROR(EINVAL);
281
+                goto fail;
282
+            }
283
+            os->packet_size = st->codec->block_align ? st->codec->block_align : 4;
284
+        }
285
+        get_private_data(os);
286
+    }
287
+
288
+    if (!c->has_video && c->min_frag_duration <= 0) {
289
+        av_log(s, AV_LOG_WARNING, "no video stream and no min frag duration set\n");
290
+        ret = AVERROR(EINVAL);
291
+    }
292
+
293
+fail:
294
+    if (ret)
295
+        ism_free(s);
296
+    return ret;
297
+}
298
+
299
+static int parse_fragment(AVFormatContext *s, const char *filename, int64_t *start_ts, int64_t *duration, int64_t *moof_size, int64_t size)
300
+{
301
+    AVIOContext *in;
302
+    int ret;
303
+    uint32_t len;
304
+    if ((ret = avio_open2(&in, filename, AVIO_FLAG_READ, &s->interrupt_callback, NULL)) < 0)
305
+        return ret;
306
+    ret = AVERROR(EIO);
307
+    *moof_size = avio_rb32(in);
308
+    if (*moof_size < 8 || *moof_size > size)
309
+        goto fail;
310
+    if (avio_rl32(in) != MKTAG('m','o','o','f'))
311
+        goto fail;
312
+    len = avio_rb32(in);
313
+    if (len > *moof_size)
314
+        goto fail;
315
+    if (avio_rl32(in) != MKTAG('m','f','h','d'))
316
+        goto fail;
317
+    avio_seek(in, len - 8, SEEK_CUR);
318
+    avio_rb32(in); /* traf size */
319
+    if (avio_rl32(in) != MKTAG('t','r','a','f'))
320
+        goto fail;
321
+    while (avio_tell(in) < *moof_size) {
322
+        uint32_t len = avio_rb32(in);
323
+        uint32_t tag = avio_rl32(in);
324
+        int64_t end = avio_tell(in) + len - 8;
325
+        if (len < 8 || len >= *moof_size)
326
+            goto fail;
327
+        if (tag == MKTAG('u','u','i','d')) {
328
+            const uint8_t tfxd[] = {
329
+                0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6,
330
+                0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2
331
+            };
332
+            uint8_t uuid[16];
333
+            avio_read(in, uuid, 16);
334
+            if (!memcmp(uuid, tfxd, 16) && len >= 8 + 16 + 4 + 16) {
335
+                avio_seek(in, 4, SEEK_CUR);
336
+                *start_ts = avio_rb64(in);
337
+                *duration = avio_rb64(in);
338
+                ret = 0;
339
+                break;
340
+            }
341
+        }
342
+        avio_seek(in, end, SEEK_SET);
343
+    }
344
+fail:
345
+    avio_close(in);
346
+    return ret;
347
+}
348
+
349
+static int add_fragment(OutputStream *os, const char *file, const char *infofile, int64_t start_time, int64_t duration, int64_t start_pos, int64_t size)
350
+{
351
+    Fragment *frag;
352
+    if (os->nb_fragments >= os->fragments_size) {
353
+        os->fragments_size = (os->fragments_size + 1) * 2;
354
+        os->fragments = av_realloc(os->fragments, sizeof(*os->fragments)*os->fragments_size);
355
+        if (!os->fragments)
356
+            return AVERROR(ENOMEM);
357
+    }
358
+    frag = av_mallocz(sizeof(*frag));
359
+    if (!frag)
360
+        return AVERROR(ENOMEM);
361
+    av_strlcpy(frag->file, file, sizeof(frag->file));
362
+    av_strlcpy(frag->infofile, infofile, sizeof(frag->infofile));
363
+    frag->start_time = start_time;
364
+    frag->duration = duration;
365
+    frag->start_pos = start_pos;
366
+    frag->size = size;
367
+    frag->n = os->fragment_index;
368
+    os->fragments[os->nb_fragments++] = frag;
369
+    os->fragment_index++;
370
+    return 0;
371
+}
372
+
373
+static int copy_moof(AVFormatContext *s, const char* infile, const char *outfile, int64_t size)
374
+{
375
+    AVIOContext *in, *out;
376
+    int ret = 0;
377
+    if ((ret = avio_open2(&in, infile, AVIO_FLAG_READ, &s->interrupt_callback, NULL)) < 0)
378
+        return ret;
379
+    if ((ret = avio_open2(&out, outfile, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL)) < 0) {
380
+        avio_close(in);
381
+        return ret;
382
+    }
383
+    while (size > 0) {
384
+        uint8_t buf[8192];
385
+        int n = FFMIN(size, sizeof(buf));
386
+        n = avio_read(in, buf, n);
387
+        if (n <= 0) {
388
+            ret = AVERROR(EIO);
389
+            break;
390
+        }
391
+        avio_write(out, buf, n);
392
+        size -= n;
393
+    }
394
+    avio_flush(out);
395
+    avio_close(out);
396
+    avio_close(in);
397
+    return ret;
398
+}
399
+
400
+static void output_chunk_list(OutputStream *os, AVIOContext *out, int final, int skip, int window_size)
401
+{
402
+    int removed = 0, i, start = 0;
403
+    if (os->nb_fragments <= 0)
404
+        return;
405
+    if (os->fragments[0]->n > 0)
406
+        removed = 1;
407
+    if (final)
408
+        skip = 0;
409
+    if (window_size)
410
+        start = FFMAX(os->nb_fragments - skip - window_size, 0);
411
+    for (i = start; i < os->nb_fragments - skip; i++) {
412
+        Fragment *frag = os->fragments[i];
413
+        if (!final || removed)
414
+            avio_printf(out, "<c t=\"%"PRIu64"\" d=\"%"PRIu64"\" />\n", frag->start_time, frag->duration);
415
+        else
416
+            avio_printf(out, "<c n=\"%d\" d=\"%"PRIu64"\" />\n", frag->n, frag->duration);
417
+    }
418
+}
419
+
420
+static int write_manifest(AVFormatContext *s, int final)
421
+{
422
+    SmoothStreamingContext *c = s->priv_data;
423
+    AVIOContext *out;
424
+    char filename[1024];
425
+    int ret, i, video_chunks = 0, audio_chunks = 0, video_streams = 0, audio_streams = 0;
426
+    int64_t duration = 0;
427
+
428
+    snprintf(filename, sizeof(filename), "%s/Manifest", s->filename);
429
+    ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL);
430
+    if (ret < 0)
431
+        return ret;
432
+    avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
433
+    for (i = 0; i < s->nb_streams; i++) {
434
+        OutputStream *os = &c->streams[i];
435
+        if (os->nb_fragments > 0) {
436
+            Fragment *last = os->fragments[os->nb_fragments - 1];
437
+            duration = last->start_time + last->duration;
438
+        }
439
+        if (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
440
+            video_chunks = os->nb_fragments;
441
+            video_streams++;
442
+        } else {
443
+            audio_chunks = os->nb_fragments;
444
+            audio_streams++;
445
+        }
446
+    }
447
+    if (!final) {
448
+        duration = 0;
449
+        video_chunks = audio_chunks = 0;
450
+    }
451
+    if (c->window_size) {
452
+        video_chunks = FFMIN(video_chunks, c->window_size);
453
+        audio_chunks = FFMIN(audio_chunks, c->window_size);
454
+    }
455
+    avio_printf(out, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" Duration=\"%"PRIu64"\"", duration);
456
+    if (!final)
457
+        avio_printf(out, " IsLive=\"true\" LookAheadFragmentCount=\"%d\" DVRWindowLength=\"0\"", c->lookahead_count);
458
+    avio_printf(out, ">\n");
459
+    if (c->has_video) {
460
+        int last = -1, index = 0;
461
+        avio_printf(out, "<StreamIndex Type=\"video\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n", video_streams, video_chunks);
462
+        for (i = 0; i < s->nb_streams; i++) {
463
+            OutputStream *os = &c->streams[i];
464
+            if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_VIDEO)
465
+                continue;
466
+            last = i;
467
+            avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%d\" FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" CodecPrivateData=\"%s\" />\n", index, s->streams[i]->codec->bit_rate, os->fourcc, s->streams[i]->codec->width, s->streams[i]->codec->height, os->private_str);
468
+            index++;
469
+        }
470
+        output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size);
471
+        avio_printf(out, "</StreamIndex>\n");
472
+    }
473
+    if (c->has_audio) {
474
+        int last = -1, index = 0;
475
+        avio_printf(out, "<StreamIndex Type=\"audio\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n", audio_streams, audio_chunks);
476
+        for (i = 0; i < s->nb_streams; i++) {
477
+            OutputStream *os = &c->streams[i];
478
+            if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO)
479
+                continue;
480
+            last = i;
481
+            avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%d\" FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" BitsPerSample=\"16\" PacketSize=\"%d\" AudioTag=\"%d\" CodecPrivateData=\"%s\" />\n", index, s->streams[i]->codec->bit_rate, os->fourcc, s->streams[i]->codec->sample_rate, s->streams[i]->codec->channels, os->packet_size, os->audio_tag, os->private_str);
482
+            index++;
483
+        }
484
+        output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size);
485
+        avio_printf(out, "</StreamIndex>\n");
486
+    }
487
+    avio_printf(out, "</SmoothStreamingMedia>\n");
488
+    avio_flush(out);
489
+    avio_close(out);
490
+    return 0;
491
+}
492
+
493
+static int ism_flush(AVFormatContext *s, int final)
494
+{
495
+    SmoothStreamingContext *c = s->priv_data;
496
+    int i, ret = 0;
497
+
498
+    for (i = 0; i < s->nb_streams; i++) {
499
+        OutputStream *os = &c->streams[i];
500
+        char filename[1024], target_filename[1024], header_filename[1024];
501
+        int64_t start_pos = os->tail_pos, size;
502
+        int64_t start_ts, duration, moof_size;
503
+        if (!os->packets_written)
504
+            continue;
505
+
506
+        snprintf(filename, sizeof(filename), "%s/temp", os->dirname);
507
+        ret = ffurl_open(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL);
508
+        if (ret < 0)
509
+            break;
510
+        os->cur_start_pos = os->tail_pos;
511
+        av_write_frame(os->ctx, NULL);
512
+        avio_flush(os->ctx->pb);
513
+        os->packets_written = 0;
514
+        if (!os->out || os->tail_out)
515
+            return AVERROR(EIO);
516
+
517
+        ffurl_close(os->out);
518
+        os->out = NULL;
519
+        size = os->tail_pos - start_pos;
520
+        if ((ret = parse_fragment(s, filename, &start_ts, &duration, &moof_size, size)) < 0)
521
+            break;
522
+        snprintf(header_filename, sizeof(header_filename), "%s/FragmentInfo(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts);
523
+        snprintf(target_filename, sizeof(target_filename), "%s/Fragments(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts);
524
+        copy_moof(s, filename, header_filename, moof_size);
525
+        rename(filename, target_filename);
526
+        add_fragment(os, target_filename, header_filename, start_ts, duration, start_pos, size);
527
+    }
528
+
529
+    if (c->window_size || (final && c->remove_at_exit)) {
530
+        for (i = 0; i < s->nb_streams; i++) {
531
+            OutputStream *os = &c->streams[i];
532
+            int j;
533
+            int remove = os->nb_fragments - c->window_size - c->extra_window_size - c->lookahead_count;
534
+            if (final && c->remove_at_exit)
535
+                remove = os->nb_fragments;
536
+            if (remove > 0) {
537
+                for (j = 0; j < remove; j++) {
538
+                    unlink(os->fragments[j]->file);
539
+                    unlink(os->fragments[j]->infofile);
540
+                    av_free(os->fragments[j]);
541
+                }
542
+                os->nb_fragments -= remove;
543
+                memmove(os->fragments, os->fragments + remove, os->nb_fragments * sizeof(*os->fragments));
544
+            }
545
+            if (final && c->remove_at_exit)
546
+                rmdir(os->dirname);
547
+        }
548
+    }
549
+
550
+    write_manifest(s, final);
551
+    return ret;
552
+}
553
+
554
+static int ism_write_packet(AVFormatContext *s, AVPacket *pkt)
555
+{
556
+    SmoothStreamingContext *c = s->priv_data;
557
+    AVStream *st = s->streams[pkt->stream_index];
558
+    OutputStream *os = &c->streams[pkt->stream_index];
559
+    int64_t end_pts = (c->nb_fragments + 1) * c->min_frag_duration;
560
+
561
+    if ((!c->has_video || st->codec->codec_type == AVMEDIA_TYPE_VIDEO) &&
562
+        av_compare_ts(pkt->pts, st->time_base,
563
+                      end_pts, AV_TIME_BASE_Q) >= 0 &&
564
+        pkt->flags & AV_PKT_FLAG_KEY && os->packets_written) {
565
+
566
+        ism_flush(s, 0);
567
+        c->nb_fragments++;
568
+    }
569
+
570
+    os->packets_written++;
571
+    return ff_write_chained(os->ctx, 0, pkt, s);
572
+}
573
+
574
+static int ism_write_trailer(AVFormatContext *s)
575
+{
576
+    SmoothStreamingContext *c = s->priv_data;
577
+    ism_flush(s, 1);
578
+
579
+    if (c->remove_at_exit) {
580
+        char filename[1024];
581
+        snprintf(filename, sizeof(filename), "%s/Manifest", s->filename);
582
+        unlink(filename);
583
+        rmdir(s->filename);
584
+    }
585
+
586
+    ism_free(s);
587
+    return 0;
588
+}
589
+
590
+#define OFFSET(x) offsetof(SmoothStreamingContext, x)
591
+#define E AV_OPT_FLAG_ENCODING_PARAM
592
+static const AVOption options[] = {
593
+    { "window_size", "number of fragments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E },
594
+    { "extra_window_size", "number of fragments kept outside of the manifest before removing from disk", OFFSET(extra_window_size), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, E },
595
+    { "lookahead_count", "number of lookahead fragments", OFFSET(lookahead_count), AV_OPT_TYPE_INT, { .i64 = 2 }, 0, INT_MAX, E },
596
+    { "min_frag_duration", "minimum fragment duration (in microseconds)", OFFSET(min_frag_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E },
597
+    { "remove_at_exit", "remove all fragments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E },
598
+    { NULL },
599
+};
600
+
601
+static const AVClass ism_class = {
602
+    .class_name = "smooth streaming muxer",
603
+    .item_name  = av_default_item_name,
604
+    .option     = options,
605
+    .version    = LIBAVUTIL_VERSION_INT,
606
+};
607
+
608
+
609
+AVOutputFormat ff_smoothstreaming_muxer = {
610
+    .name           = "smoothstreaming",
611
+    .long_name      = NULL_IF_CONFIG_SMALL("Smooth Streaming Muxer"),
612
+    .priv_data_size = sizeof(SmoothStreamingContext),
613
+    .audio_codec    = AV_CODEC_ID_AAC,
614
+    .video_codec    = AV_CODEC_ID_H264,
615
+    .flags          = AVFMT_GLOBALHEADER | AVFMT_NOFILE,
616
+    .write_header   = ism_write_header,
617
+    .write_packet   = ism_write_packet,
618
+    .write_trailer  = ism_write_trailer,
619
+    .priv_class     = &ism_class,
620
+};
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/avutil.h"
31 31
 
32 32
 #define LIBAVFORMAT_VERSION_MAJOR 54
33
-#define LIBAVFORMAT_VERSION_MINOR 15
33
+#define LIBAVFORMAT_VERSION_MINOR 16
34 34
 #define LIBAVFORMAT_VERSION_MICRO  0
35 35
 
36 36
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \