blob: dabd1ea304c8ed9fe7a8235f898b974e652b4fa2
1 | /* |
2 | * Live smooth streaming fragmenter |
3 | * Copyright (c) 2012 Martin Storsjo |
4 | * |
5 | * This file is part of FFmpeg. |
6 | * |
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. |
11 | * |
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. |
16 | * |
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 |
20 | */ |
21 | |
22 | #include "config.h" |
23 | #include <float.h> |
24 | #if HAVE_UNISTD_H |
25 | #include <unistd.h> |
26 | #endif |
27 | |
28 | #include "avformat.h" |
29 | #include "avio_internal.h" |
30 | #include "internal.h" |
31 | #include "os_support.h" |
32 | #include "avc.h" |
33 | #include "url.h" |
34 | #include "isom.h" |
35 | |
36 | #include "libavutil/opt.h" |
37 | #include "libavutil/avstring.h" |
38 | #include "libavutil/file.h" |
39 | #include "libavutil/mathematics.h" |
40 | #include "libavutil/intreadwrite.h" |
41 | |
42 | typedef struct Fragment { |
43 | char file[1024]; |
44 | char infofile[1024]; |
45 | int64_t start_time, duration; |
46 | int n; |
47 | int64_t start_pos, size; |
48 | } Fragment; |
49 | |
50 | typedef struct OutputStream { |
51 | AVFormatContext *ctx; |
52 | int ctx_inited; |
53 | char dirname[1024]; |
54 | uint8_t iobuf[32768]; |
55 | URLContext *out; // Current output stream where all output is written |
56 | URLContext *out2; // Auxiliary output stream where all output is also written |
57 | URLContext *tail_out; // The actual main output stream, if we're currently seeked back to write elsewhere |
58 | int64_t tail_pos, cur_pos, cur_start_pos; |
59 | int packets_written; |
60 | const char *stream_type_tag; |
61 | int nb_fragments, fragments_size, fragment_index; |
62 | Fragment **fragments; |
63 | |
64 | const char *fourcc; |
65 | char *private_str; |
66 | int packet_size; |
67 | int audio_tag; |
68 | } OutputStream; |
69 | |
70 | typedef struct SmoothStreamingContext { |
71 | const AVClass *class; /* Class for private options. */ |
72 | int window_size; |
73 | int extra_window_size; |
74 | int lookahead_count; |
75 | int min_frag_duration; |
76 | int remove_at_exit; |
77 | OutputStream *streams; |
78 | int has_video, has_audio; |
79 | int nb_fragments; |
80 | } SmoothStreamingContext; |
81 | |
82 | static int ism_write(void *opaque, uint8_t *buf, int buf_size) |
83 | { |
84 | OutputStream *os = opaque; |
85 | if (os->out) |
86 | ffurl_write(os->out, buf, buf_size); |
87 | if (os->out2) |
88 | ffurl_write(os->out2, buf, buf_size); |
89 | os->cur_pos += buf_size; |
90 | if (os->cur_pos >= os->tail_pos) |
91 | os->tail_pos = os->cur_pos; |
92 | return buf_size; |
93 | } |
94 | |
95 | static int64_t ism_seek(void *opaque, int64_t offset, int whence) |
96 | { |
97 | OutputStream *os = opaque; |
98 | int i; |
99 | if (whence != SEEK_SET) |
100 | return AVERROR(ENOSYS); |
101 | if (os->tail_out) { |
102 | if (os->out) { |
103 | ffurl_close(os->out); |
104 | } |
105 | if (os->out2) { |
106 | ffurl_close(os->out2); |
107 | } |
108 | os->out = os->tail_out; |
109 | os->out2 = NULL; |
110 | os->tail_out = NULL; |
111 | } |
112 | if (offset >= os->cur_start_pos) { |
113 | if (os->out) |
114 | ffurl_seek(os->out, offset - os->cur_start_pos, SEEK_SET); |
115 | os->cur_pos = offset; |
116 | return offset; |
117 | } |
118 | for (i = os->nb_fragments - 1; i >= 0; i--) { |
119 | Fragment *frag = os->fragments[i]; |
120 | if (offset >= frag->start_pos && offset < frag->start_pos + frag->size) { |
121 | int ret; |
122 | AVDictionary *opts = NULL; |
123 | os->tail_out = os->out; |
124 | 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 | av_dict_free(&opts); |
128 | if (ret < 0) { |
129 | os->out = os->tail_out; |
130 | os->tail_out = NULL; |
131 | return ret; |
132 | } |
133 | 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 | av_dict_free(&opts); |
137 | ffurl_seek(os->out, offset - frag->start_pos, SEEK_SET); |
138 | if (os->out2) |
139 | ffurl_seek(os->out2, offset - frag->start_pos, SEEK_SET); |
140 | os->cur_pos = offset; |
141 | return offset; |
142 | } |
143 | } |
144 | return AVERROR(EIO); |
145 | } |
146 | |
147 | static void get_private_data(OutputStream *os) |
148 | { |
149 | AVCodecParameters *par = os->ctx->streams[0]->codecpar; |
150 | uint8_t *ptr = par->extradata; |
151 | int size = par->extradata_size; |
152 | int i; |
153 | if (par->codec_id == AV_CODEC_ID_H264) { |
154 | ff_avc_write_annexb_extradata(ptr, &ptr, &size); |
155 | if (!ptr) |
156 | ptr = par->extradata; |
157 | } |
158 | if (!ptr) |
159 | return; |
160 | os->private_str = av_mallocz(2*size + 1); |
161 | if (!os->private_str) |
162 | goto fail; |
163 | for (i = 0; i < size; i++) |
164 | snprintf(&os->private_str[2*i], 3, "%02x", ptr[i]); |
165 | fail: |
166 | if (ptr != par->extradata) |
167 | av_free(ptr); |
168 | } |
169 | |
170 | static void ism_free(AVFormatContext *s) |
171 | { |
172 | SmoothStreamingContext *c = s->priv_data; |
173 | int i, j; |
174 | if (!c->streams) |
175 | return; |
176 | for (i = 0; i < s->nb_streams; i++) { |
177 | OutputStream *os = &c->streams[i]; |
178 | ffurl_close(os->out); |
179 | ffurl_close(os->out2); |
180 | ffurl_close(os->tail_out); |
181 | os->out = os->out2 = os->tail_out = NULL; |
182 | if (os->ctx && os->ctx_inited) |
183 | av_write_trailer(os->ctx); |
184 | if (os->ctx && os->ctx->pb) |
185 | av_freep(&os->ctx->pb); |
186 | if (os->ctx) |
187 | avformat_free_context(os->ctx); |
188 | av_freep(&os->private_str); |
189 | for (j = 0; j < os->nb_fragments; j++) |
190 | av_freep(&os->fragments[j]); |
191 | av_freep(&os->fragments); |
192 | } |
193 | av_freep(&c->streams); |
194 | } |
195 | |
196 | static void output_chunk_list(OutputStream *os, AVIOContext *out, int final, int skip, int window_size) |
197 | { |
198 | int removed = 0, i, start = 0; |
199 | if (os->nb_fragments <= 0) |
200 | return; |
201 | if (os->fragments[0]->n > 0) |
202 | removed = 1; |
203 | if (final) |
204 | skip = 0; |
205 | if (window_size) |
206 | start = FFMAX(os->nb_fragments - skip - window_size, 0); |
207 | for (i = start; i < os->nb_fragments - skip; i++) { |
208 | Fragment *frag = os->fragments[i]; |
209 | if (!final || removed) |
210 | avio_printf(out, "<c t=\"%"PRIu64"\" d=\"%"PRIu64"\" />\n", frag->start_time, frag->duration); |
211 | else |
212 | avio_printf(out, "<c n=\"%d\" d=\"%"PRIu64"\" />\n", frag->n, frag->duration); |
213 | } |
214 | } |
215 | |
216 | static int write_manifest(AVFormatContext *s, int final) |
217 | { |
218 | SmoothStreamingContext *c = s->priv_data; |
219 | AVIOContext *out; |
220 | char filename[1024], temp_filename[1024]; |
221 | int ret, i, video_chunks = 0, audio_chunks = 0, video_streams = 0, audio_streams = 0; |
222 | int64_t duration = 0; |
223 | |
224 | snprintf(filename, sizeof(filename), "%s/Manifest", s->filename); |
225 | snprintf(temp_filename, sizeof(temp_filename), "%s/Manifest.tmp", s->filename); |
226 | ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL); |
227 | if (ret < 0) { |
228 | av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename); |
229 | return ret; |
230 | } |
231 | avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); |
232 | for (i = 0; i < s->nb_streams; i++) { |
233 | OutputStream *os = &c->streams[i]; |
234 | if (os->nb_fragments > 0) { |
235 | Fragment *last = os->fragments[os->nb_fragments - 1]; |
236 | duration = last->start_time + last->duration; |
237 | } |
238 | if (s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { |
239 | video_chunks = os->nb_fragments; |
240 | video_streams++; |
241 | } else { |
242 | audio_chunks = os->nb_fragments; |
243 | audio_streams++; |
244 | } |
245 | } |
246 | if (!final) { |
247 | duration = 0; |
248 | video_chunks = audio_chunks = 0; |
249 | } |
250 | if (c->window_size) { |
251 | video_chunks = FFMIN(video_chunks, c->window_size); |
252 | audio_chunks = FFMIN(audio_chunks, c->window_size); |
253 | } |
254 | avio_printf(out, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" Duration=\"%"PRIu64"\"", duration); |
255 | if (!final) |
256 | avio_printf(out, " IsLive=\"true\" LookAheadFragmentCount=\"%d\" DVRWindowLength=\"0\"", c->lookahead_count); |
257 | avio_printf(out, ">\n"); |
258 | if (c->has_video) { |
259 | int last = -1, index = 0; |
260 | avio_printf(out, "<StreamIndex Type=\"video\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n", video_streams, video_chunks); |
261 | for (i = 0; i < s->nb_streams; i++) { |
262 | OutputStream *os = &c->streams[i]; |
263 | if (s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) |
264 | continue; |
265 | last = i; |
266 | avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%"PRId64"\" FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" CodecPrivateData=\"%s\" />\n", index, (int64_t)s->streams[i]->codecpar->bit_rate, os->fourcc, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height, os->private_str); |
267 | index++; |
268 | } |
269 | output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size); |
270 | avio_printf(out, "</StreamIndex>\n"); |
271 | } |
272 | if (c->has_audio) { |
273 | int last = -1, index = 0; |
274 | avio_printf(out, "<StreamIndex Type=\"audio\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n", audio_streams, audio_chunks); |
275 | for (i = 0; i < s->nb_streams; i++) { |
276 | OutputStream *os = &c->streams[i]; |
277 | if (s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) |
278 | continue; |
279 | last = i; |
280 | avio_printf(out, "<QualityLevel Index=\"%d\" Bitrate=\"%"PRId64"\" FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" BitsPerSample=\"16\" PacketSize=\"%d\" AudioTag=\"%d\" CodecPrivateData=\"%s\" />\n", index, (int64_t)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); |
281 | index++; |
282 | } |
283 | output_chunk_list(&c->streams[last], out, final, c->lookahead_count, c->window_size); |
284 | avio_printf(out, "</StreamIndex>\n"); |
285 | } |
286 | avio_printf(out, "</SmoothStreamingMedia>\n"); |
287 | avio_flush(out); |
288 | ff_format_io_close(s, &out); |
289 | return ff_rename(temp_filename, filename, s); |
290 | } |
291 | |
292 | static int ism_write_header(AVFormatContext *s) |
293 | { |
294 | SmoothStreamingContext *c = s->priv_data; |
295 | int ret = 0, i; |
296 | AVOutputFormat *oformat; |
297 | |
298 | if (mkdir(s->filename, 0777) == -1 && errno != EEXIST) { |
299 | ret = AVERROR(errno); |
300 | av_log(s, AV_LOG_ERROR, "mkdir failed\n"); |
301 | goto fail; |
302 | } |
303 | |
304 | oformat = av_guess_format("ismv", NULL, NULL); |
305 | if (!oformat) { |
306 | ret = AVERROR_MUXER_NOT_FOUND; |
307 | goto fail; |
308 | } |
309 | |
310 | c->streams = av_mallocz_array(s->nb_streams, sizeof(*c->streams)); |
311 | if (!c->streams) { |
312 | ret = AVERROR(ENOMEM); |
313 | goto fail; |
314 | } |
315 | |
316 | for (i = 0; i < s->nb_streams; i++) { |
317 | OutputStream *os = &c->streams[i]; |
318 | AVFormatContext *ctx; |
319 | AVStream *st; |
320 | AVDictionary *opts = NULL; |
321 | |
322 | if (!s->streams[i]->codecpar->bit_rate) { |
323 | av_log(s, AV_LOG_ERROR, "No bit rate set for stream %d\n", i); |
324 | ret = AVERROR(EINVAL); |
325 | goto fail; |
326 | } |
327 | snprintf(os->dirname, sizeof(os->dirname), "%s/QualityLevels(%"PRId64")", s->filename, (int64_t)s->streams[i]->codecpar->bit_rate); |
328 | if (mkdir(os->dirname, 0777) == -1 && errno != EEXIST) { |
329 | ret = AVERROR(errno); |
330 | av_log(s, AV_LOG_ERROR, "mkdir failed\n"); |
331 | goto fail; |
332 | } |
333 | |
334 | ctx = avformat_alloc_context(); |
335 | if (!ctx || ff_copy_whiteblacklists(ctx, s) < 0) { |
336 | ret = AVERROR(ENOMEM); |
337 | goto fail; |
338 | } |
339 | os->ctx = ctx; |
340 | ctx->oformat = oformat; |
341 | ctx->interrupt_callback = s->interrupt_callback; |
342 | |
343 | if (!(st = avformat_new_stream(ctx, NULL))) { |
344 | ret = AVERROR(ENOMEM); |
345 | goto fail; |
346 | } |
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 | ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, ism_write, ism_seek); |
352 | if (!ctx->pb) { |
353 | ret = AVERROR(ENOMEM); |
354 | goto fail; |
355 | } |
356 | |
357 | av_dict_set_int(&opts, "ism_lookahead", c->lookahead_count, 0); |
358 | av_dict_set(&opts, "movflags", "frag_custom", 0); |
359 | if ((ret = avformat_write_header(ctx, &opts)) < 0) { |
360 | goto fail; |
361 | } |
362 | os->ctx_inited = 1; |
363 | avio_flush(ctx->pb); |
364 | av_dict_free(&opts); |
365 | s->streams[i]->time_base = st->time_base; |
366 | if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { |
367 | c->has_video = 1; |
368 | os->stream_type_tag = "video"; |
369 | if (st->codecpar->codec_id == AV_CODEC_ID_H264) { |
370 | os->fourcc = "H264"; |
371 | } else if (st->codecpar->codec_id == AV_CODEC_ID_VC1) { |
372 | os->fourcc = "WVC1"; |
373 | } else { |
374 | av_log(s, AV_LOG_ERROR, "Unsupported video codec\n"); |
375 | ret = AVERROR(EINVAL); |
376 | goto fail; |
377 | } |
378 | } else { |
379 | c->has_audio = 1; |
380 | os->stream_type_tag = "audio"; |
381 | if (st->codecpar->codec_id == AV_CODEC_ID_AAC) { |
382 | os->fourcc = "AACL"; |
383 | os->audio_tag = 0xff; |
384 | } else if (st->codecpar->codec_id == AV_CODEC_ID_WMAPRO) { |
385 | os->fourcc = "WMAP"; |
386 | os->audio_tag = 0x0162; |
387 | } else { |
388 | av_log(s, AV_LOG_ERROR, "Unsupported audio codec\n"); |
389 | ret = AVERROR(EINVAL); |
390 | goto fail; |
391 | } |
392 | os->packet_size = st->codecpar->block_align ? st->codecpar->block_align : 4; |
393 | } |
394 | get_private_data(os); |
395 | } |
396 | |
397 | if (!c->has_video && c->min_frag_duration <= 0) { |
398 | av_log(s, AV_LOG_WARNING, "no video stream and no min frag duration set\n"); |
399 | ret = AVERROR(EINVAL); |
400 | goto fail; |
401 | } |
402 | ret = write_manifest(s, 0); |
403 | |
404 | fail: |
405 | if (ret) |
406 | ism_free(s); |
407 | return ret; |
408 | } |
409 | |
410 | static int parse_fragment(AVFormatContext *s, const char *filename, int64_t *start_ts, int64_t *duration, int64_t *moof_size, int64_t size) |
411 | { |
412 | AVIOContext *in; |
413 | int ret; |
414 | uint32_t len; |
415 | if ((ret = s->io_open(s, &in, filename, AVIO_FLAG_READ, NULL)) < 0) |
416 | return ret; |
417 | ret = AVERROR(EIO); |
418 | *moof_size = avio_rb32(in); |
419 | if (*moof_size < 8 || *moof_size > size) |
420 | goto fail; |
421 | if (avio_rl32(in) != MKTAG('m','o','o','f')) |
422 | goto fail; |
423 | len = avio_rb32(in); |
424 | if (len > *moof_size) |
425 | goto fail; |
426 | if (avio_rl32(in) != MKTAG('m','f','h','d')) |
427 | goto fail; |
428 | avio_seek(in, len - 8, SEEK_CUR); |
429 | avio_rb32(in); /* traf size */ |
430 | if (avio_rl32(in) != MKTAG('t','r','a','f')) |
431 | goto fail; |
432 | while (avio_tell(in) < *moof_size) { |
433 | uint32_t len = avio_rb32(in); |
434 | uint32_t tag = avio_rl32(in); |
435 | int64_t end = avio_tell(in) + len - 8; |
436 | if (len < 8 || len >= *moof_size) |
437 | goto fail; |
438 | if (tag == MKTAG('u','u','i','d')) { |
439 | static const uint8_t tfxd[] = { |
440 | 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6, |
441 | 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2 |
442 | }; |
443 | uint8_t uuid[16]; |
444 | avio_read(in, uuid, 16); |
445 | if (!memcmp(uuid, tfxd, 16) && len >= 8 + 16 + 4 + 16) { |
446 | avio_seek(in, 4, SEEK_CUR); |
447 | *start_ts = avio_rb64(in); |
448 | *duration = avio_rb64(in); |
449 | ret = 0; |
450 | break; |
451 | } |
452 | } |
453 | avio_seek(in, end, SEEK_SET); |
454 | } |
455 | fail: |
456 | ff_format_io_close(s, &in); |
457 | return ret; |
458 | } |
459 | |
460 | 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) |
461 | { |
462 | int err; |
463 | Fragment *frag; |
464 | if (os->nb_fragments >= os->fragments_size) { |
465 | os->fragments_size = (os->fragments_size + 1) * 2; |
466 | if ((err = av_reallocp(&os->fragments, sizeof(*os->fragments) * |
467 | os->fragments_size)) < 0) { |
468 | os->fragments_size = 0; |
469 | os->nb_fragments = 0; |
470 | return err; |
471 | } |
472 | } |
473 | frag = av_mallocz(sizeof(*frag)); |
474 | if (!frag) |
475 | return AVERROR(ENOMEM); |
476 | av_strlcpy(frag->file, file, sizeof(frag->file)); |
477 | av_strlcpy(frag->infofile, infofile, sizeof(frag->infofile)); |
478 | frag->start_time = start_time; |
479 | frag->duration = duration; |
480 | frag->start_pos = start_pos; |
481 | frag->size = size; |
482 | frag->n = os->fragment_index; |
483 | os->fragments[os->nb_fragments++] = frag; |
484 | os->fragment_index++; |
485 | return 0; |
486 | } |
487 | |
488 | static int copy_moof(AVFormatContext *s, const char* infile, const char *outfile, int64_t size) |
489 | { |
490 | AVIOContext *in, *out; |
491 | int ret = 0; |
492 | if ((ret = s->io_open(s, &in, infile, AVIO_FLAG_READ, NULL)) < 0) |
493 | return ret; |
494 | if ((ret = s->io_open(s, &out, outfile, AVIO_FLAG_WRITE, NULL)) < 0) { |
495 | ff_format_io_close(s, &in); |
496 | return ret; |
497 | } |
498 | while (size > 0) { |
499 | uint8_t buf[8192]; |
500 | int n = FFMIN(size, sizeof(buf)); |
501 | n = avio_read(in, buf, n); |
502 | if (n <= 0) { |
503 | ret = AVERROR(EIO); |
504 | break; |
505 | } |
506 | avio_write(out, buf, n); |
507 | size -= n; |
508 | } |
509 | avio_flush(out); |
510 | ff_format_io_close(s, &out); |
511 | ff_format_io_close(s, &in); |
512 | return ret; |
513 | } |
514 | |
515 | static int ism_flush(AVFormatContext *s, int final) |
516 | { |
517 | SmoothStreamingContext *c = s->priv_data; |
518 | int i, ret = 0; |
519 | |
520 | for (i = 0; i < s->nb_streams; i++) { |
521 | OutputStream *os = &c->streams[i]; |
522 | char filename[1024], target_filename[1024], header_filename[1024]; |
523 | int64_t size; |
524 | int64_t start_ts, duration, moof_size; |
525 | if (!os->packets_written) |
526 | continue; |
527 | |
528 | snprintf(filename, sizeof(filename), "%s/temp", os->dirname); |
529 | ret = ffurl_open_whitelist(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL, s->protocol_whitelist, s->protocol_blacklist, NULL); |
530 | if (ret < 0) |
531 | break; |
532 | os->cur_start_pos = os->tail_pos; |
533 | av_write_frame(os->ctx, NULL); |
534 | avio_flush(os->ctx->pb); |
535 | os->packets_written = 0; |
536 | if (!os->out || os->tail_out) |
537 | return AVERROR(EIO); |
538 | |
539 | ffurl_close(os->out); |
540 | os->out = NULL; |
541 | size = os->tail_pos - os->cur_start_pos; |
542 | if ((ret = parse_fragment(s, filename, &start_ts, &duration, &moof_size, size)) < 0) |
543 | break; |
544 | snprintf(header_filename, sizeof(header_filename), "%s/FragmentInfo(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts); |
545 | snprintf(target_filename, sizeof(target_filename), "%s/Fragments(%s=%"PRIu64")", os->dirname, os->stream_type_tag, start_ts); |
546 | copy_moof(s, filename, header_filename, moof_size); |
547 | ret = ff_rename(filename, target_filename, s); |
548 | if (ret < 0) |
549 | break; |
550 | add_fragment(os, target_filename, header_filename, start_ts, duration, |
551 | os->cur_start_pos, size); |
552 | } |
553 | |
554 | if (c->window_size || (final && c->remove_at_exit)) { |
555 | for (i = 0; i < s->nb_streams; i++) { |
556 | OutputStream *os = &c->streams[i]; |
557 | int j; |
558 | int remove = os->nb_fragments - c->window_size - c->extra_window_size - c->lookahead_count; |
559 | if (final && c->remove_at_exit) |
560 | remove = os->nb_fragments; |
561 | if (remove > 0) { |
562 | for (j = 0; j < remove; j++) { |
563 | unlink(os->fragments[j]->file); |
564 | unlink(os->fragments[j]->infofile); |
565 | av_freep(&os->fragments[j]); |
566 | } |
567 | os->nb_fragments -= remove; |
568 | memmove(os->fragments, os->fragments + remove, os->nb_fragments * sizeof(*os->fragments)); |
569 | } |
570 | if (final && c->remove_at_exit) |
571 | rmdir(os->dirname); |
572 | } |
573 | } |
574 | |
575 | if (ret >= 0) |
576 | ret = write_manifest(s, final); |
577 | return ret; |
578 | } |
579 | |
580 | static int ism_write_packet(AVFormatContext *s, AVPacket *pkt) |
581 | { |
582 | SmoothStreamingContext *c = s->priv_data; |
583 | AVStream *st = s->streams[pkt->stream_index]; |
584 | OutputStream *os = &c->streams[pkt->stream_index]; |
585 | int64_t end_dts = (c->nb_fragments + 1) * (int64_t) c->min_frag_duration; |
586 | int ret; |
587 | |
588 | if (st->first_dts == AV_NOPTS_VALUE) |
589 | st->first_dts = pkt->dts; |
590 | |
591 | if ((!c->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && |
592 | av_compare_ts(pkt->dts - st->first_dts, st->time_base, |
593 | end_dts, AV_TIME_BASE_Q) >= 0 && |
594 | pkt->flags & AV_PKT_FLAG_KEY && os->packets_written) { |
595 | |
596 | if ((ret = ism_flush(s, 0)) < 0) |
597 | return ret; |
598 | c->nb_fragments++; |
599 | } |
600 | |
601 | os->packets_written++; |
602 | return ff_write_chained(os->ctx, 0, pkt, s, 0); |
603 | } |
604 | |
605 | static int ism_write_trailer(AVFormatContext *s) |
606 | { |
607 | SmoothStreamingContext *c = s->priv_data; |
608 | ism_flush(s, 1); |
609 | |
610 | if (c->remove_at_exit) { |
611 | char filename[1024]; |
612 | snprintf(filename, sizeof(filename), "%s/Manifest", s->filename); |
613 | unlink(filename); |
614 | rmdir(s->filename); |
615 | } |
616 | |
617 | ism_free(s); |
618 | return 0; |
619 | } |
620 | |
621 | #define OFFSET(x) offsetof(SmoothStreamingContext, x) |
622 | #define E AV_OPT_FLAG_ENCODING_PARAM |
623 | static const AVOption options[] = { |
624 | { "window_size", "number of fragments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E }, |
625 | { "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 }, |
626 | { "lookahead_count", "number of lookahead fragments", OFFSET(lookahead_count), AV_OPT_TYPE_INT, { .i64 = 2 }, 0, INT_MAX, E }, |
627 | { "min_frag_duration", "minimum fragment duration (in microseconds)", OFFSET(min_frag_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E }, |
628 | { "remove_at_exit", "remove all fragments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, |
629 | { NULL }, |
630 | }; |
631 | |
632 | static const AVClass ism_class = { |
633 | .class_name = "smooth streaming muxer", |
634 | .item_name = av_default_item_name, |
635 | .option = options, |
636 | .version = LIBAVUTIL_VERSION_INT, |
637 | }; |
638 | |
639 | |
640 | AVOutputFormat ff_smoothstreaming_muxer = { |
641 | .name = "smoothstreaming", |
642 | .long_name = NULL_IF_CONFIG_SMALL("Smooth Streaming Muxer"), |
643 | .priv_data_size = sizeof(SmoothStreamingContext), |
644 | .audio_codec = AV_CODEC_ID_AAC, |
645 | .video_codec = AV_CODEC_ID_H264, |
646 | .flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE, |
647 | .write_header = ism_write_header, |
648 | .write_packet = ism_write_packet, |
649 | .write_trailer = ism_write_trailer, |
650 | .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, |
651 | .priv_class = &ism_class, |
652 | }; |
653 |