2 * Live smooth streaming fragmenter
3 * Copyright (c) 2012 Martin Storsjo
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * 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 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * 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 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30 #include "os_support.h"
35 #include "libavutil/opt.h"
36 #include "libavutil/avstring.h"
37 #include "libavutil/file.h"
38 #include "libavutil/mathematics.h"
39 #include "libavutil/intreadwrite.h"
44 int64_t start_time
, duration
;
46 int64_t start_pos
, size
;
54 URLContext
*out
; // Current output stream where all output is written
55 URLContext
*out2
; // Auxiliary output stream where all output is also written
56 URLContext
*tail_out
; // The actual main output stream, if we're currently seeked back to write elsewhere
57 int64_t tail_pos
, cur_pos
, cur_start_pos
;
59 const char *stream_type_tag
;
60 int nb_fragments
, fragments_size
, fragment_index
;
70 const AVClass
*class; /* Class for private options. */
72 int extra_window_size
;
74 int min_frag_duration
;
76 OutputStream
*streams
;
77 int has_video
, has_audio
;
79 } SmoothStreamingContext
;
81 static int ism_write(void *opaque
, uint8_t *buf
, int buf_size
)
83 OutputStream
*os
= opaque
;
85 ffurl_write(os
->out
, buf
, buf_size
);
87 ffurl_write(os
->out2
, buf
, buf_size
);
88 os
->cur_pos
+= buf_size
;
89 if (os
->cur_pos
>= os
->tail_pos
)
90 os
->tail_pos
= os
->cur_pos
;
94 static int64_t ism_seek(void *opaque
, int64_t offset
, int whence
)
96 OutputStream
*os
= opaque
;
98 if (whence
!= SEEK_SET
)
99 return AVERROR(ENOSYS
);
102 ffurl_close(os
->out
);
105 ffurl_close(os
->out2
);
107 os
->out
= os
->tail_out
;
111 if (offset
>= os
->cur_start_pos
) {
113 ffurl_seek(os
->out
, offset
- os
->cur_start_pos
, SEEK_SET
);
114 os
->cur_pos
= offset
;
117 for (i
= os
->nb_fragments
- 1; i
>= 0; i
--) {
118 Fragment
*frag
= os
->fragments
[i
];
119 if (offset
>= frag
->start_pos
&& offset
< frag
->start_pos
+ frag
->size
) {
121 AVDictionary
*opts
= NULL
;
122 os
->tail_out
= os
->out
;
123 av_dict_set(&opts
, "truncate", "0", 0);
124 ret
= ffurl_open(&os
->out
, frag
->file
, AVIO_FLAG_READ_WRITE
, &os
->ctx
->interrupt_callback
, &opts
);
127 os
->out
= os
->tail_out
;
131 av_dict_set(&opts
, "truncate", "0", 0);
132 ffurl_open(&os
->out2
, frag
->infofile
, AVIO_FLAG_READ_WRITE
, &os
->ctx
->interrupt_callback
, &opts
);
134 ffurl_seek(os
->out
, offset
- frag
->start_pos
, SEEK_SET
);
136 ffurl_seek(os
->out2
, offset
- frag
->start_pos
, SEEK_SET
);
137 os
->cur_pos
= offset
;
144 static void get_private_data(OutputStream
*os
)
146 AVCodecContext
*codec
= os
->ctx
->streams
[0]->codec
;
147 uint8_t *ptr
= codec
->extradata
;
148 int size
= codec
->extradata_size
;
150 if (codec
->codec_id
== AV_CODEC_ID_H264
) {
151 ff_avc_write_annexb_extradata(ptr
, &ptr
, &size
);
153 ptr
= codec
->extradata
;
157 os
->private_str
= av_mallocz(2*size
+ 1);
158 if (!os
->private_str
)
160 for (i
= 0; i
< size
; i
++)
161 snprintf(&os
->private_str
[2*i
], 3, "%02x", ptr
[i
]);
163 if (ptr
!= codec
->extradata
)
167 static void ism_free(AVFormatContext
*s
)
169 SmoothStreamingContext
*c
= s
->priv_data
;
173 for (i
= 0; i
< s
->nb_streams
; i
++) {
174 OutputStream
*os
= &c
->streams
[i
];
175 ffurl_close(os
->out
);
176 ffurl_close(os
->out2
);
177 ffurl_close(os
->tail_out
);
178 os
->out
= os
->out2
= os
->tail_out
= NULL
;
179 if (os
->ctx
&& os
->ctx_inited
)
180 av_write_trailer(os
->ctx
);
181 if (os
->ctx
&& os
->ctx
->pb
)
182 av_free(os
->ctx
->pb
);
184 avformat_free_context(os
->ctx
);
185 av_free(os
->private_str
);
186 for (j
= 0; j
< os
->nb_fragments
; j
++)
187 av_free(os
->fragments
[j
]);
188 av_free(os
->fragments
);
190 av_freep(&c
->streams
);
193 static void output_chunk_list(OutputStream
*os
, AVIOContext
*out
, int final
, int skip
, int window_size
)
195 int removed
= 0, i
, start
= 0;
196 if (os
->nb_fragments
<= 0)
198 if (os
->fragments
[0]->n
> 0)
203 start
= FFMAX(os
->nb_fragments
- skip
- window_size
, 0);
204 for (i
= start
; i
< os
->nb_fragments
- skip
; i
++) {
205 Fragment
*frag
= os
->fragments
[i
];
206 if (!final
|| removed
)
207 avio_printf(out
, "<c t=\"%"PRIu64
"\" d=\"%"PRIu64
"\" />\n", frag
->start_time
, frag
->duration
);
209 avio_printf(out
, "<c n=\"%d\" d=\"%"PRIu64
"\" />\n", frag
->n
, frag
->duration
);
213 static int write_manifest(AVFormatContext
*s
, int final
)
215 SmoothStreamingContext
*c
= s
->priv_data
;
217 char filename
[1024], temp_filename
[1024];
218 int ret
, i
, video_chunks
= 0, audio_chunks
= 0, video_streams
= 0, audio_streams
= 0;
219 int64_t duration
= 0;
221 snprintf(filename
, sizeof(filename
), "%s/Manifest", s
->filename
);
222 snprintf(temp_filename
, sizeof(temp_filename
), "%s/Manifest.tmp", s
->filename
);
223 ret
= avio_open2(&out
, temp_filename
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
);
225 av_log(s
, AV_LOG_ERROR
, "Unable to open %s for writing\n", temp_filename
);
228 avio_printf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
229 for (i
= 0; i
< s
->nb_streams
; i
++) {
230 OutputStream
*os
= &c
->streams
[i
];
231 if (os
->nb_fragments
> 0) {
232 Fragment
*last
= os
->fragments
[os
->nb_fragments
- 1];
233 duration
= last
->start_time
+ last
->duration
;
235 if (s
->streams
[i
]->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
) {
236 video_chunks
= os
->nb_fragments
;
239 audio_chunks
= os
->nb_fragments
;
245 video_chunks
= audio_chunks
= 0;
247 if (c
->window_size
) {
248 video_chunks
= FFMIN(video_chunks
, c
->window_size
);
249 audio_chunks
= FFMIN(audio_chunks
, c
->window_size
);
251 avio_printf(out
, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" Duration=\"%"PRIu64
"\"", duration
);
253 avio_printf(out
, " IsLive=\"true\" LookAheadFragmentCount=\"%d\" DVRWindowLength=\"0\"", c
->lookahead_count
);
254 avio_printf(out
, ">\n");
256 int last
= -1, index
= 0;
257 avio_printf(out
, "<StreamIndex Type=\"video\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n", video_streams
, video_chunks
);
258 for (i
= 0; i
< s
->nb_streams
; i
++) {
259 OutputStream
*os
= &c
->streams
[i
];
260 if (s
->streams
[i
]->codec
->codec_type
!= AVMEDIA_TYPE_VIDEO
)
263 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
);
266 output_chunk_list(&c
->streams
[last
], out
, final
, c
->lookahead_count
, c
->window_size
);
267 avio_printf(out
, "</StreamIndex>\n");
270 int last
= -1, index
= 0;
271 avio_printf(out
, "<StreamIndex Type=\"audio\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n", audio_streams
, audio_chunks
);
272 for (i
= 0; i
< s
->nb_streams
; i
++) {
273 OutputStream
*os
= &c
->streams
[i
];
274 if (s
->streams
[i
]->codec
->codec_type
!= AVMEDIA_TYPE_AUDIO
)
277 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
);
280 output_chunk_list(&c
->streams
[last
], out
, final
, c
->lookahead_count
, c
->window_size
);
281 avio_printf(out
, "</StreamIndex>\n");
283 avio_printf(out
, "</SmoothStreamingMedia>\n");
286 return ff_rename(temp_filename
, filename
, s
);
289 static int ism_write_header(AVFormatContext
*s
)
291 SmoothStreamingContext
*c
= s
->priv_data
;
293 AVOutputFormat
*oformat
;
295 if (mkdir(s
->filename
, 0777) == -1 && errno
!= EEXIST
) {
296 ret
= AVERROR(errno
);
297 av_log(s
, AV_LOG_ERROR
, "mkdir failed\n");
301 oformat
= av_guess_format("ismv", NULL
, NULL
);
303 ret
= AVERROR_MUXER_NOT_FOUND
;
307 c
->streams
= av_mallocz_array(s
->nb_streams
, sizeof(*c
->streams
));
309 ret
= AVERROR(ENOMEM
);
313 for (i
= 0; i
< s
->nb_streams
; i
++) {
314 OutputStream
*os
= &c
->streams
[i
];
315 AVFormatContext
*ctx
;
317 AVDictionary
*opts
= NULL
;
319 if (!s
->streams
[i
]->codec
->bit_rate
) {
320 av_log(s
, AV_LOG_ERROR
, "No bit rate set for stream %d\n", i
);
321 ret
= AVERROR(EINVAL
);
324 snprintf(os
->dirname
, sizeof(os
->dirname
), "%s/QualityLevels(%d)", s
->filename
, s
->streams
[i
]->codec
->bit_rate
);
325 if (mkdir(os
->dirname
, 0777) == -1 && errno
!= EEXIST
) {
326 ret
= AVERROR(errno
);
327 av_log(s
, AV_LOG_ERROR
, "mkdir failed\n");
331 ctx
= avformat_alloc_context();
333 ret
= AVERROR(ENOMEM
);
337 ctx
->oformat
= oformat
;
338 ctx
->interrupt_callback
= s
->interrupt_callback
;
340 if (!(st
= avformat_new_stream(ctx
, NULL
))) {
341 ret
= AVERROR(ENOMEM
);
344 avcodec_copy_context(st
->codec
, s
->streams
[i
]->codec
);
345 st
->sample_aspect_ratio
= s
->streams
[i
]->sample_aspect_ratio
;
346 st
->time_base
= s
->streams
[i
]->time_base
;
348 ctx
->pb
= avio_alloc_context(os
->iobuf
, sizeof(os
->iobuf
), AVIO_FLAG_WRITE
, os
, NULL
, ism_write
, ism_seek
);
350 ret
= AVERROR(ENOMEM
);
354 av_dict_set_int(&opts
, "ism_lookahead", c
->lookahead_count
, 0);
355 av_dict_set(&opts
, "movflags", "frag_custom", 0);
356 if ((ret
= avformat_write_header(ctx
, &opts
)) < 0) {
362 s
->streams
[i
]->time_base
= st
->time_base
;
363 if (st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
) {
365 os
->stream_type_tag
= "video";
366 if (st
->codec
->codec_id
== AV_CODEC_ID_H264
) {
368 } else if (st
->codec
->codec_id
== AV_CODEC_ID_VC1
) {
371 av_log(s
, AV_LOG_ERROR
, "Unsupported video codec\n");
372 ret
= AVERROR(EINVAL
);
377 os
->stream_type_tag
= "audio";
378 if (st
->codec
->codec_id
== AV_CODEC_ID_AAC
) {
380 os
->audio_tag
= 0xff;
381 } else if (st
->codec
->codec_id
== AV_CODEC_ID_WMAPRO
) {
383 os
->audio_tag
= 0x0162;
385 av_log(s
, AV_LOG_ERROR
, "Unsupported audio codec\n");
386 ret
= AVERROR(EINVAL
);
389 os
->packet_size
= st
->codec
->block_align
? st
->codec
->block_align
: 4;
391 get_private_data(os
);
394 if (!c
->has_video
&& c
->min_frag_duration
<= 0) {
395 av_log(s
, AV_LOG_WARNING
, "no video stream and no min frag duration set\n");
396 ret
= AVERROR(EINVAL
);
398 ret
= write_manifest(s
, 0);
406 static int parse_fragment(AVFormatContext
*s
, const char *filename
, int64_t *start_ts
, int64_t *duration
, int64_t *moof_size
, int64_t size
)
411 if ((ret
= avio_open2(&in
, filename
, AVIO_FLAG_READ
, &s
->interrupt_callback
, NULL
)) < 0)
414 *moof_size
= avio_rb32(in
);
415 if (*moof_size
< 8 || *moof_size
> size
)
417 if (avio_rl32(in
) != MKTAG('m','o','o','f'))
420 if (len
> *moof_size
)
422 if (avio_rl32(in
) != MKTAG('m','f','h','d'))
424 avio_seek(in
, len
- 8, SEEK_CUR
);
425 avio_rb32(in
); /* traf size */
426 if (avio_rl32(in
) != MKTAG('t','r','a','f'))
428 while (avio_tell(in
) < *moof_size
) {
429 uint32_t len
= avio_rb32(in
);
430 uint32_t tag
= avio_rl32(in
);
431 int64_t end
= avio_tell(in
) + len
- 8;
432 if (len
< 8 || len
>= *moof_size
)
434 if (tag
== MKTAG('u','u','i','d')) {
435 static const uint8_t tfxd
[] = {
436 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6,
437 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2
440 avio_read(in
, uuid
, 16);
441 if (!memcmp(uuid
, tfxd
, 16) && len
>= 8 + 16 + 4 + 16) {
442 avio_seek(in
, 4, SEEK_CUR
);
443 *start_ts
= avio_rb64(in
);
444 *duration
= avio_rb64(in
);
449 avio_seek(in
, end
, SEEK_SET
);
456 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
)
460 if (os
->nb_fragments
>= os
->fragments_size
) {
461 os
->fragments_size
= (os
->fragments_size
+ 1) * 2;
462 if ((err
= av_reallocp(&os
->fragments
, sizeof(*os
->fragments
) *
463 os
->fragments_size
)) < 0) {
464 os
->fragments_size
= 0;
465 os
->nb_fragments
= 0;
469 frag
= av_mallocz(sizeof(*frag
));
471 return AVERROR(ENOMEM
);
472 av_strlcpy(frag
->file
, file
, sizeof(frag
->file
));
473 av_strlcpy(frag
->infofile
, infofile
, sizeof(frag
->infofile
));
474 frag
->start_time
= start_time
;
475 frag
->duration
= duration
;
476 frag
->start_pos
= start_pos
;
478 frag
->n
= os
->fragment_index
;
479 os
->fragments
[os
->nb_fragments
++] = frag
;
480 os
->fragment_index
++;
484 static int copy_moof(AVFormatContext
*s
, const char* infile
, const char *outfile
, int64_t size
)
486 AVIOContext
*in
, *out
;
488 if ((ret
= avio_open2(&in
, infile
, AVIO_FLAG_READ
, &s
->interrupt_callback
, NULL
)) < 0)
490 if ((ret
= avio_open2(&out
, outfile
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
)) < 0) {
496 int n
= FFMIN(size
, sizeof(buf
));
497 n
= avio_read(in
, buf
, n
);
502 avio_write(out
, buf
, n
);
511 static int ism_flush(AVFormatContext
*s
, int final
)
513 SmoothStreamingContext
*c
= s
->priv_data
;
516 for (i
= 0; i
< s
->nb_streams
; i
++) {
517 OutputStream
*os
= &c
->streams
[i
];
518 char filename
[1024], target_filename
[1024], header_filename
[1024];
520 int64_t start_ts
, duration
, moof_size
;
521 if (!os
->packets_written
)
524 snprintf(filename
, sizeof(filename
), "%s/temp", os
->dirname
);
525 ret
= ffurl_open(&os
->out
, filename
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
);
528 os
->cur_start_pos
= os
->tail_pos
;
529 av_write_frame(os
->ctx
, NULL
);
530 avio_flush(os
->ctx
->pb
);
531 os
->packets_written
= 0;
532 if (!os
->out
|| os
->tail_out
)
535 ffurl_close(os
->out
);
537 size
= os
->tail_pos
- os
->cur_start_pos
;
538 if ((ret
= parse_fragment(s
, filename
, &start_ts
, &duration
, &moof_size
, size
)) < 0)
540 snprintf(header_filename
, sizeof(header_filename
), "%s/FragmentInfo(%s=%"PRIu64
")", os
->dirname
, os
->stream_type_tag
, start_ts
);
541 snprintf(target_filename
, sizeof(target_filename
), "%s/Fragments(%s=%"PRIu64
")", os
->dirname
, os
->stream_type_tag
, start_ts
);
542 copy_moof(s
, filename
, header_filename
, moof_size
);
543 ret
= ff_rename(filename
, target_filename
, s
);
546 add_fragment(os
, target_filename
, header_filename
, start_ts
, duration
,
547 os
->cur_start_pos
, size
);
550 if (c
->window_size
|| (final
&& c
->remove_at_exit
)) {
551 for (i
= 0; i
< s
->nb_streams
; i
++) {
552 OutputStream
*os
= &c
->streams
[i
];
554 int remove
= os
->nb_fragments
- c
->window_size
- c
->extra_window_size
- c
->lookahead_count
;
555 if (final
&& c
->remove_at_exit
)
556 remove
= os
->nb_fragments
;
558 for (j
= 0; j
< remove
; j
++) {
559 unlink(os
->fragments
[j
]->file
);
560 unlink(os
->fragments
[j
]->infofile
);
561 av_free(os
->fragments
[j
]);
563 os
->nb_fragments
-= remove
;
564 memmove(os
->fragments
, os
->fragments
+ remove
, os
->nb_fragments
* sizeof(*os
->fragments
));
566 if (final
&& c
->remove_at_exit
)
572 ret
= write_manifest(s
, final
);
576 static int ism_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
578 SmoothStreamingContext
*c
= s
->priv_data
;
579 AVStream
*st
= s
->streams
[pkt
->stream_index
];
580 OutputStream
*os
= &c
->streams
[pkt
->stream_index
];
581 int64_t end_dts
= (c
->nb_fragments
+ 1) * (int64_t) c
->min_frag_duration
;
584 if (st
->first_dts
== AV_NOPTS_VALUE
)
585 st
->first_dts
= pkt
->dts
;
587 if ((!c
->has_video
|| st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
) &&
588 av_compare_ts(pkt
->dts
- st
->first_dts
, st
->time_base
,
589 end_dts
, AV_TIME_BASE_Q
) >= 0 &&
590 pkt
->flags
& AV_PKT_FLAG_KEY
&& os
->packets_written
) {
592 if ((ret
= ism_flush(s
, 0)) < 0)
597 os
->packets_written
++;
598 return ff_write_chained(os
->ctx
, 0, pkt
, s
, 0);
601 static int ism_write_trailer(AVFormatContext
*s
)
603 SmoothStreamingContext
*c
= s
->priv_data
;
606 if (c
->remove_at_exit
) {
608 snprintf(filename
, sizeof(filename
), "%s/Manifest", s
->filename
);
617 #define OFFSET(x) offsetof(SmoothStreamingContext, x)
618 #define E AV_OPT_FLAG_ENCODING_PARAM
619 static const AVOption options
[] = {
620 { "window_size", "number of fragments kept in the manifest", OFFSET(window_size
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, INT_MAX
, E
},
621 { "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
},
622 { "lookahead_count", "number of lookahead fragments", OFFSET(lookahead_count
), AV_OPT_TYPE_INT
, { .i64
= 2 }, 0, INT_MAX
, E
},
623 { "min_frag_duration", "minimum fragment duration (in microseconds)", OFFSET(min_frag_duration
), AV_OPT_TYPE_INT64
, { .i64
= 5000000 }, 0, INT_MAX
, E
},
624 { "remove_at_exit", "remove all fragments when finished", OFFSET(remove_at_exit
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, 1, E
},
628 static const AVClass ism_class
= {
629 .class_name
= "smooth streaming muxer",
630 .item_name
= av_default_item_name
,
632 .version
= LIBAVUTIL_VERSION_INT
,
636 AVOutputFormat ff_smoothstreaming_muxer
= {
637 .name
= "smoothstreaming",
638 .long_name
= NULL_IF_CONFIG_SMALL("Smooth Streaming Muxer"),
639 .priv_data_size
= sizeof(SmoothStreamingContext
),
640 .audio_codec
= AV_CODEC_ID_AAC
,
641 .video_codec
= AV_CODEC_ID_H264
,
642 .flags
= AVFMT_GLOBALHEADER
| AVFMT_NOFILE
,
643 .write_header
= ism_write_header
,
644 .write_packet
= ism_write_packet
,
645 .write_trailer
= ism_write_trailer
,
646 .codec_tag
= (const AVCodecTag
* const []){ ff_mp4_obj_type
, 0 },
647 .priv_class
= &ism_class
,