smoothstreamingenc.c 23.8 KB
Newer Older
1 2 3 4
/*
 * Live smooth streaming fragmenter
 * Copyright (c) 2012 Martin Storsjo
 *
5
 * This file is part of FFmpeg.
6
 *
7
 * FFmpeg is free software; you can redistribute it and/or
8 9 10 11
 * 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.
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
13 14 15 16 17
 * 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
18
 * License along with FFmpeg; if not, write to the Free Software
19 20 21 22 23 24 25 26 27 28
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "config.h"
#include <float.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "avformat.h"
29
#include "avio_internal.h"
30 31 32 33
#include "internal.h"
#include "os_support.h"
#include "avc.h"
#include "url.h"
34
#include "isom.h"
35 36 37

#include "libavutil/opt.h"
#include "libavutil/avstring.h"
38
#include "libavutil/file.h"
39 40 41
#include "libavutil/mathematics.h"
#include "libavutil/intreadwrite.h"

42
typedef struct Fragment {
43 44 45 46 47 48 49
    char file[1024];
    char infofile[1024];
    int64_t start_time, duration;
    int n;
    int64_t start_pos, size;
} Fragment;

50
typedef struct OutputStream {
51 52 53 54 55
    AVFormatContext *ctx;
    int ctx_inited;
    char dirname[1024];
    uint8_t iobuf[32768];
    URLContext *out;  // Current output stream where all output is written
Diego Biurrun's avatar
Diego Biurrun committed
56
    URLContext *out2; // Auxiliary output stream where all output is also written
57 58 59 60 61 62 63 64 65 66 67 68 69
    URLContext *tail_out; // The actual main output stream, if we're currently seeked back to write elsewhere
    int64_t tail_pos, cur_pos, cur_start_pos;
    int packets_written;
    const char *stream_type_tag;
    int nb_fragments, fragments_size, fragment_index;
    Fragment **fragments;

    const char *fourcc;
    char *private_str;
    int packet_size;
    int audio_tag;
} OutputStream;

70
typedef struct SmoothStreamingContext {
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    const AVClass *class;  /* Class for private options. */
    int window_size;
    int extra_window_size;
    int lookahead_count;
    int min_frag_duration;
    int remove_at_exit;
    OutputStream *streams;
    int has_video, has_audio;
    int nb_fragments;
} SmoothStreamingContext;

static int ism_write(void *opaque, uint8_t *buf, int buf_size)
{
    OutputStream *os = opaque;
    if (os->out)
        ffurl_write(os->out, buf, buf_size);
    if (os->out2)
        ffurl_write(os->out2, buf, buf_size);
    os->cur_pos += buf_size;
    if (os->cur_pos >= os->tail_pos)
        os->tail_pos = os->cur_pos;
    return buf_size;
}

static int64_t ism_seek(void *opaque, int64_t offset, int whence)
{
    OutputStream *os = opaque;
    int i;
    if (whence != SEEK_SET)
        return AVERROR(ENOSYS);
    if (os->tail_out) {
        if (os->out) {
            ffurl_close(os->out);
        }
        if (os->out2) {
            ffurl_close(os->out2);
        }
        os->out = os->tail_out;
        os->out2 = NULL;
        os->tail_out = NULL;
    }
    if (offset >= os->cur_start_pos) {
113 114
        if (os->out)
            ffurl_seek(os->out, offset - os->cur_start_pos, SEEK_SET);
115 116 117 118 119 120 121 122 123 124
        os->cur_pos = offset;
        return offset;
    }
    for (i = os->nb_fragments - 1; i >= 0; i--) {
        Fragment *frag = os->fragments[i];
        if (offset >= frag->start_pos && offset < frag->start_pos + frag->size) {
            int ret;
            AVDictionary *opts = NULL;
            os->tail_out = os->out;
            av_dict_set(&opts, "truncate", "0", 0);
125
            ret = ffurl_open_whitelist(&os->out, frag->file, AVIO_FLAG_WRITE,
126
                                       &os->ctx->interrupt_callback, &opts, os->ctx->protocol_whitelist, os->ctx->protocol_blacklist, NULL);
127 128 129 130 131 132 133
            av_dict_free(&opts);
            if (ret < 0) {
                os->out = os->tail_out;
                os->tail_out = NULL;
                return ret;
            }
            av_dict_set(&opts, "truncate", "0", 0);
134
            ffurl_open_whitelist(&os->out2, frag->infofile, AVIO_FLAG_WRITE,
135
                                 &os->ctx->interrupt_callback, &opts, os->ctx->protocol_whitelist, os->ctx->protocol_blacklist, NULL);
136 137 138 139 140 141 142 143 144 145 146 147 148
            av_dict_free(&opts);
            ffurl_seek(os->out, offset - frag->start_pos, SEEK_SET);
            if (os->out2)
                ffurl_seek(os->out2, offset - frag->start_pos, SEEK_SET);
            os->cur_pos = offset;
            return offset;
        }
    }
    return AVERROR(EIO);
}

static void get_private_data(OutputStream *os)
{
149 150 151
    AVCodecParameters *par = os->ctx->streams[0]->codecpar;
    uint8_t *ptr = par->extradata;
    int size = par->extradata_size;
152
    int i;
153
    if (par->codec_id == AV_CODEC_ID_H264) {
154 155
        ff_avc_write_annexb_extradata(ptr, &ptr, &size);
        if (!ptr)
156
            ptr = par->extradata;
157 158 159 160
    }
    if (!ptr)
        return;
    os->private_str = av_mallocz(2*size + 1);
161
    if (!os->private_str)
162
        goto fail;
163 164
    for (i = 0; i < size; i++)
        snprintf(&os->private_str[2*i], 3, "%02x", ptr[i]);
165
fail:
166
    if (ptr != par->extradata)
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
        av_free(ptr);
}

static void ism_free(AVFormatContext *s)
{
    SmoothStreamingContext *c = s->priv_data;
    int i, j;
    if (!c->streams)
        return;
    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
        ffurl_close(os->out);
        ffurl_close(os->out2);
        ffurl_close(os->tail_out);
        os->out = os->out2 = os->tail_out = NULL;
        if (os->ctx && os->ctx_inited)
            av_write_trailer(os->ctx);
        if (os->ctx && os->ctx->pb)
185
            avio_context_free(&os->ctx->pb);
186
        avformat_free_context(os->ctx);
187
        av_freep(&os->private_str);
188
        for (j = 0; j < os->nb_fragments; j++)
189 190
            av_freep(&os->fragments[j]);
        av_freep(&os->fragments);
191 192 193 194
    }
    av_freep(&c->streams);
}

195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
static void output_chunk_list(OutputStream *os, AVIOContext *out, int final, int skip, int window_size)
{
    int removed = 0, i, start = 0;
    if (os->nb_fragments <= 0)
        return;
    if (os->fragments[0]->n > 0)
        removed = 1;
    if (final)
        skip = 0;
    if (window_size)
        start = FFMAX(os->nb_fragments - skip - window_size, 0);
    for (i = start; i < os->nb_fragments - skip; i++) {
        Fragment *frag = os->fragments[i];
        if (!final || removed)
            avio_printf(out, "<c t=\"%"PRIu64"\" d=\"%"PRIu64"\" />\n", frag->start_time, frag->duration);
        else
            avio_printf(out, "<c n=\"%d\" d=\"%"PRIu64"\" />\n", frag->n, frag->duration);
    }
}

static int write_manifest(AVFormatContext *s, int final)
{
    SmoothStreamingContext *c = s->priv_data;
    AVIOContext *out;
219
    char filename[1024], temp_filename[1024];
220 221 222
    int ret, i, video_chunks = 0, audio_chunks = 0, video_streams = 0, audio_streams = 0;
    int64_t duration = 0;

223 224
    snprintf(filename, sizeof(filename), "%s/Manifest", s->url);
    snprintf(temp_filename, sizeof(temp_filename), "%s/Manifest.tmp", s->url);
225
    ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL);
226
    if (ret < 0) {
227
        av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename);
228
        return ret;
229
    }
230 231 232 233 234 235 236
    avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
        if (os->nb_fragments > 0) {
            Fragment *last = os->fragments[os->nb_fragments - 1];
            duration = last->start_time + last->duration;
        }
237
        if (s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
            video_chunks = os->nb_fragments;
            video_streams++;
        } else {
            audio_chunks = os->nb_fragments;
            audio_streams++;
        }
    }
    if (!final) {
        duration = 0;
        video_chunks = audio_chunks = 0;
    }
    if (c->window_size) {
        video_chunks = FFMIN(video_chunks, c->window_size);
        audio_chunks = FFMIN(audio_chunks, c->window_size);
    }
    avio_printf(out, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" Duration=\"%"PRIu64"\"", duration);
    if (!final)
        avio_printf(out, " IsLive=\"true\" LookAheadFragmentCount=\"%d\" DVRWindowLength=\"0\"", c->lookahead_count);
    avio_printf(out, ">\n");
    if (c->has_video) {
        int last = -1, index = 0;
        avio_printf(out, "<StreamIndex Type=\"video\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n", video_streams, video_chunks);
        for (i = 0; i < s->nb_streams; i++) {
            OutputStream *os = &c->streams[i];
262
            if (s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
263 264
                continue;
            last = i;
265
            avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%"PRId64"\" FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" CodecPrivateData=\"%s\" />\n", index, s->streams[i]->codecpar->bit_rate, os->fourcc, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height, os->private_str);
266 267 268 269 270 271 272 273 274 275
            index++;
        }
        output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size);
        avio_printf(out, "</StreamIndex>\n");
    }
    if (c->has_audio) {
        int last = -1, index = 0;
        avio_printf(out, "<StreamIndex Type=\"audio\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n", audio_streams, audio_chunks);
        for (i = 0; i < s->nb_streams; i++) {
            OutputStream *os = &c->streams[i];
276
            if (s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
277 278
                continue;
            last = i;
279
            avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%"PRId64"\" FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" BitsPerSample=\"16\" PacketSize=\"%d\" AudioTag=\"%d\" CodecPrivateData=\"%s\" />\n", index, s->streams[i]->codecpar->bit_rate, os->fourcc, s->streams[i]->codecpar->sample_rate, s->streams[i]->codecpar->channels, os->packet_size, os->audio_tag, os->private_str);
280 281 282 283 284 285 286
            index++;
        }
        output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size);
        avio_printf(out, "</StreamIndex>\n");
    }
    avio_printf(out, "</SmoothStreamingMedia>\n");
    avio_flush(out);
287
    ff_format_io_close(s, &out);
288
    return ff_rename(temp_filename, filename, s);
289 290
}

291 292 293 294
static int ism_write_header(AVFormatContext *s)
{
    SmoothStreamingContext *c = s->priv_data;
    int ret = 0, i;
295
    ff_const59 AVOutputFormat *oformat;
296

297
    if (mkdir(s->url, 0777) == -1 && errno != EEXIST) {
298
        ret = AVERROR(errno);
299
        av_log(s, AV_LOG_ERROR, "mkdir failed\n");
300 301
        goto fail;
    }
302 303 304 305 306 307 308

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

309
    c->streams = av_mallocz_array(s->nb_streams, sizeof(*c->streams));
310 311 312 313 314 315 316 317 318 319 320
    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;

321
        if (!s->streams[i]->codecpar->bit_rate) {
322 323 324 325 326
            av_log(s, AV_LOG_WARNING, "No bit rate set for stream %d\n", i);
            // create a tmp name for the directory of fragments
            snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(Tmp_%d)", s->url, i);
        } else {
            snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(%"PRId64")", s->url, s->streams[i]->codecpar->bit_rate);
327
        }
328

329
        if (mkdir(os->dirname, 0777) == -1 && errno != EEXIST) {
330 331 332 333
            ret = AVERROR(errno);
            av_log(s, AV_LOG_ERROR, "mkdir failed\n");
            goto fail;
        }
334

335
        os->ctx = ctx = avformat_alloc_context();
336
        if (!ctx || ff_copy_whiteblacklists(ctx, s) < 0) {
337 338 339 340 341 342 343 344 345 346
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        ctx->oformat = oformat;
        ctx->interrupt_callback = s->interrupt_callback;

        if (!(st = avformat_new_stream(ctx, NULL))) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
347
        avcodec_parameters_copy(st->codecpar, s->streams[i]->codecpar);
348
        st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
349
        st->time_base = s->streams[i]->time_base;
350 351 352 353 354 355 356

        ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, ism_write, ism_seek);
        if (!ctx->pb) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }

357
        av_dict_set_int(&opts, "ism_lookahead", c->lookahead_count, 0);
358
        av_dict_set(&opts, "movflags", "frag_custom", 0);
359 360 361
        ret = avformat_write_header(ctx, &opts);
        av_dict_free(&opts);
        if (ret < 0) {
362 363 364 365 366
             goto fail;
        }
        os->ctx_inited = 1;
        avio_flush(ctx->pb);
        s->streams[i]->time_base = st->time_base;
367
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
368 369
            c->has_video = 1;
            os->stream_type_tag = "video";
370
            if (st->codecpar->codec_id == AV_CODEC_ID_H264) {
371
                os->fourcc = "H264";
372
            } else if (st->codecpar->codec_id == AV_CODEC_ID_VC1) {
373 374 375 376 377 378 379 380 381
                os->fourcc = "WVC1";
            } else {
                av_log(s, AV_LOG_ERROR, "Unsupported video codec\n");
                ret = AVERROR(EINVAL);
                goto fail;
            }
        } else {
            c->has_audio = 1;
            os->stream_type_tag = "audio";
382
            if (st->codecpar->codec_id == AV_CODEC_ID_AAC) {
383 384
                os->fourcc = "AACL";
                os->audio_tag = 0xff;
385
            } else if (st->codecpar->codec_id == AV_CODEC_ID_WMAPRO) {
386 387 388 389 390 391 392
                os->fourcc = "WMAP";
                os->audio_tag = 0x0162;
            } else {
                av_log(s, AV_LOG_ERROR, "Unsupported audio codec\n");
                ret = AVERROR(EINVAL);
                goto fail;
            }
393
            os->packet_size = st->codecpar->block_align ? st->codecpar->block_align : 4;
394 395 396 397 398 399 400
        }
        get_private_data(os);
    }

    if (!c->has_video && c->min_frag_duration <= 0) {
        av_log(s, AV_LOG_WARNING, "no video stream and no min frag duration set\n");
        ret = AVERROR(EINVAL);
401
        goto fail;
402
    }
403
    ret = write_manifest(s, 0);
404 405 406 407 408 409 410 411 412 413 414 415

fail:
    if (ret)
        ism_free(s);
    return ret;
}

static int parse_fragment(AVFormatContext *s, const char *filename, int64_t *start_ts, int64_t *duration, int64_t *moof_size, int64_t size)
{
    AVIOContext *in;
    int ret;
    uint32_t len;
416
    if ((ret = s->io_open(s, &in, filename, AVIO_FLAG_READ, NULL)) < 0)
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
        return ret;
    ret = AVERROR(EIO);
    *moof_size = avio_rb32(in);
    if (*moof_size < 8 || *moof_size > size)
        goto fail;
    if (avio_rl32(in) != MKTAG('m','o','o','f'))
        goto fail;
    len = avio_rb32(in);
    if (len > *moof_size)
        goto fail;
    if (avio_rl32(in) != MKTAG('m','f','h','d'))
        goto fail;
    avio_seek(in, len - 8, SEEK_CUR);
    avio_rb32(in); /* traf size */
    if (avio_rl32(in) != MKTAG('t','r','a','f'))
        goto fail;
    while (avio_tell(in) < *moof_size) {
        uint32_t len = avio_rb32(in);
        uint32_t tag = avio_rl32(in);
        int64_t end = avio_tell(in) + len - 8;
        if (len < 8 || len >= *moof_size)
            goto fail;
        if (tag == MKTAG('u','u','i','d')) {
440
            static const uint8_t tfxd[] = {
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
                0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6,
                0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2
            };
            uint8_t uuid[16];
            avio_read(in, uuid, 16);
            if (!memcmp(uuid, tfxd, 16) && len >= 8 + 16 + 4 + 16) {
                avio_seek(in, 4, SEEK_CUR);
                *start_ts = avio_rb64(in);
                *duration = avio_rb64(in);
                ret = 0;
                break;
            }
        }
        avio_seek(in, end, SEEK_SET);
    }
fail:
457
    ff_format_io_close(s, &in);
458 459 460 461 462
    return ret;
}

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)
{
463
    int err;
464 465 466
    Fragment *frag;
    if (os->nb_fragments >= os->fragments_size) {
        os->fragments_size = (os->fragments_size + 1) * 2;
467
        if ((err = av_reallocp(&os->fragments, sizeof(*os->fragments) *
468 469 470
                               os->fragments_size)) < 0) {
            os->fragments_size = 0;
            os->nb_fragments = 0;
471
            return err;
472
        }
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
    }
    frag = av_mallocz(sizeof(*frag));
    if (!frag)
        return AVERROR(ENOMEM);
    av_strlcpy(frag->file, file, sizeof(frag->file));
    av_strlcpy(frag->infofile, infofile, sizeof(frag->infofile));
    frag->start_time = start_time;
    frag->duration = duration;
    frag->start_pos = start_pos;
    frag->size = size;
    frag->n = os->fragment_index;
    os->fragments[os->nb_fragments++] = frag;
    os->fragment_index++;
    return 0;
}

static int copy_moof(AVFormatContext *s, const char* infile, const char *outfile, int64_t size)
{
    AVIOContext *in, *out;
    int ret = 0;
493
    if ((ret = s->io_open(s, &in, infile, AVIO_FLAG_READ, NULL)) < 0)
494
        return ret;
495 496
    if ((ret = s->io_open(s, &out, outfile, AVIO_FLAG_WRITE, NULL)) < 0) {
        ff_format_io_close(s, &in);
497 498 499 500 501 502 503 504 505 506 507 508 509 510
        return ret;
    }
    while (size > 0) {
        uint8_t buf[8192];
        int n = FFMIN(size, sizeof(buf));
        n = avio_read(in, buf, n);
        if (n <= 0) {
            ret = AVERROR(EIO);
            break;
        }
        avio_write(out, buf, n);
        size -= n;
    }
    avio_flush(out);
511 512
    ff_format_io_close(s, &out);
    ff_format_io_close(s, &in);
513 514 515 516 517 518 519 520 521 522
    return ret;
}

static int ism_flush(AVFormatContext *s, int final)
{
    SmoothStreamingContext *c = s->priv_data;
    int i, ret = 0;

    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
523
        char filename[1024], target_filename[1024], header_filename[1024], curr_dirname[1024];
524
        int64_t size;
525 526 527 528 529
        int64_t start_ts, duration, moof_size;
        if (!os->packets_written)
            continue;

        snprintf(filename, sizeof(filename), "%s/temp", os->dirname);
530
        ret = ffurl_open_whitelist(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist, NULL);
531 532 533 534 535 536 537 538 539 540 541
        if (ret < 0)
            break;
        os->cur_start_pos = os->tail_pos;
        av_write_frame(os->ctx, NULL);
        avio_flush(os->ctx->pb);
        os->packets_written = 0;
        if (!os->out || os->tail_out)
            return AVERROR(EIO);

        ffurl_close(os->out);
        os->out = NULL;
542
        size = os->tail_pos - os->cur_start_pos;
543 544
        if ((ret = parse_fragment(s, filename, &start_ts, &duration, &moof_size, size)) < 0)
            break;
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564

        if (!s->streams[i]->codecpar->bit_rate) {
            int64_t bitrate = (int64_t) size * 8 * AV_TIME_BASE / av_rescale_q(duration, s->streams[i]->time_base, AV_TIME_BASE_Q);
            if (!bitrate) {
                av_log(s, AV_LOG_ERROR, "calculating bitrate got zero.\n");
                ret = AVERROR(EINVAL);
                return ret;
            }

            av_log(s, AV_LOG_DEBUG, "calculated bitrate: %"PRId64"\n", bitrate);
            s->streams[i]->codecpar->bit_rate = bitrate;
            memcpy(curr_dirname, os->dirname, sizeof(os->dirname));
            snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(%"PRId64")", s->url, s->streams[i]->codecpar->bit_rate);
            snprintf(filename, sizeof(filename), "%s/temp", os->dirname);

            // rename the tmp folder back to the correct name since we now have the bitrate
            if ((ret = ff_rename((const char*)curr_dirname,  os->dirname, s)) < 0)
                return ret;
        }

565 566 567
        snprintf(header_filename, sizeof(header_filename), "%s/FragmentInfo(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts);
        snprintf(target_filename, sizeof(target_filename), "%s/Fragments(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts);
        copy_moof(s, filename, header_filename, moof_size);
568
        ret = ff_rename(filename, target_filename, s);
569 570
        if (ret < 0)
            break;
571 572
        add_fragment(os, target_filename, header_filename, start_ts, duration,
                     os->cur_start_pos, size);
573 574 575 576 577 578 579 580 581 582 583 584 585
    }

    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_fragments - c->window_size - c->extra_window_size - c->lookahead_count;
            if (final && c->remove_at_exit)
                remove = os->nb_fragments;
            if (remove > 0) {
                for (j = 0; j < remove; j++) {
                    unlink(os->fragments[j]->file);
                    unlink(os->fragments[j]->infofile);
586
                    av_freep(&os->fragments[j]);
587 588 589 590 591 592 593 594 595
                }
                os->nb_fragments -= remove;
                memmove(os->fragments, os->fragments + remove, os->nb_fragments * sizeof(*os->fragments));
            }
            if (final && c->remove_at_exit)
                rmdir(os->dirname);
        }
    }

596 597
    if (ret >= 0)
        ret = write_manifest(s, final);
598 599 600 601 602 603 604 605
    return ret;
}

static int ism_write_packet(AVFormatContext *s, AVPacket *pkt)
{
    SmoothStreamingContext *c = s->priv_data;
    AVStream *st = s->streams[pkt->stream_index];
    OutputStream *os = &c->streams[pkt->stream_index];
606
    int64_t end_dts = (c->nb_fragments + 1) * (int64_t) c->min_frag_duration;
607
    int ret;
608

609 610 611
    if (st->first_dts == AV_NOPTS_VALUE)
        st->first_dts = pkt->dts;

612
    if ((!c->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) &&
613 614
        av_compare_ts(pkt->dts - st->first_dts, st->time_base,
                      end_dts, AV_TIME_BASE_Q) >= 0 &&
615 616
        pkt->flags & AV_PKT_FLAG_KEY && os->packets_written) {

617 618
        if ((ret = ism_flush(s, 0)) < 0)
            return ret;
619 620 621 622
        c->nb_fragments++;
    }

    os->packets_written++;
623
    return ff_write_chained(os->ctx, 0, pkt, s, 0);
624 625 626 627 628 629 630 631 632
}

static int ism_write_trailer(AVFormatContext *s)
{
    SmoothStreamingContext *c = s->priv_data;
    ism_flush(s, 1);

    if (c->remove_at_exit) {
        char filename[1024];
633
        snprintf(filename, sizeof(filename), "%s/Manifest", s->url);
634
        unlink(filename);
635
        rmdir(s->url);
636 637 638 639 640 641 642 643 644 645 646 647 648
    }

    ism_free(s);
    return 0;
}

#define OFFSET(x) offsetof(SmoothStreamingContext, x)
#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
    { "window_size", "number of fragments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E },
    { "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 },
    { "lookahead_count", "number of lookahead fragments", OFFSET(lookahead_count), AV_OPT_TYPE_INT, { .i64 = 2 }, 0, INT_MAX, E },
    { "min_frag_duration", "minimum fragment duration (in microseconds)", OFFSET(min_frag_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E },
649
    { "remove_at_exit", "remove all fragments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    { NULL },
};

static const AVClass ism_class = {
    .class_name = "smooth streaming muxer",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};


AVOutputFormat ff_smoothstreaming_muxer = {
    .name           = "smoothstreaming",
    .long_name      = NULL_IF_CONFIG_SMALL("Smooth Streaming Muxer"),
    .priv_data_size = sizeof(SmoothStreamingContext),
    .audio_codec    = AV_CODEC_ID_AAC,
    .video_codec    = AV_CODEC_ID_H264,
    .flags          = AVFMT_GLOBALHEADER | AVFMT_NOFILE,
    .write_header   = ism_write_header,
    .write_packet   = ism_write_packet,
    .write_trailer  = ism_write_trailer,
    .priv_class     = &ism_class,
};