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
34
39
47
48 typedef struct {
54 URLContext *
out2;
// Auxiliary output stream where all output is also written
55 URLContext *
tail_out;
// The actual main output stream, if we're currently seeked back to write elsewhere
61
67
69 const AVClass *
class;
/* Class for private options. */
79
81 {
90 return buf_size;
91 }
92
94 {
96 int i;
97 if (whence != SEEK_SET)
102 }
105 }
109 }
115 }
118 if (offset >= frag->
start_pos && offset < frag->start_pos + frag->
size) {
125 if (ret < 0) {
129 }
138 }
139 }
141 }
142
144 {
148 int i;
151 if (!ptr)
153 }
154 if (!ptr)
155 return;
157 for (i = 0; i <
size; i++)
161 }
162
164 {
166 int i, j;
168 return;
185 }
187 }
188
190 {
191 int removed = 0, i,
start = 0;
193 return;
195 removed = 1;
196 if (final)
197 skip = 0;
198 if (window_size)
202 if (!final || removed)
204 else
206 }
207 }
208
210 {
213 char filename[1024], temp_filename[1024];
214 int ret, i, video_chunks = 0, audio_chunks = 0, video_streams = 0, audio_streams = 0;
216
218 snprintf(temp_filename,
sizeof(temp_filename),
"%s/Manifest.tmp", s->
filename);
220 if (ret < 0) {
223 }
224 avio_printf(out,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
230 }
233 video_streams++;
234 } else {
236 audio_streams++;
237 }
238 }
239 if (!final) {
240 duration = 0;
241 video_chunks = audio_chunks = 0;
242 }
246 }
247 avio_printf(out,
"<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" Duration=\"%"PRIu64
"\"", duration);
248 if (!final)
252 int last = -1,
index = 0;
253 avio_printf(out,
"<StreamIndex Type=\"video\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n", video_streams, video_chunks);
257 continue;
258 last = i;
261 }
264 }
266 int last = -1,
index = 0;
267 avio_printf(out,
"<StreamIndex Type=\"audio\" QualityLevels=\"%d\" Chunks=\"%d\" Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n", audio_streams, audio_chunks);
271 continue;
272 last = i;
275 }
278 }
282 rename(temp_filename, filename);
283 return 0;
284 }
285
287 {
291
295 goto fail;
296 }
297
299 if (!oformat) {
301 goto fail;
302 }
303
307 goto fail;
308 }
309
316
320 goto fail;
321 }
323 if (mkdir(os->
dirname, 0777) < 0) {
326 goto fail;
327 }
328
330 if (!ctx) {
332 goto fail;
333 }
337
340 goto fail;
341 }
344
348 goto fail;
349 }
350
355 goto fail;
356 }
368 } else {
371 goto fail;
372 }
373 } else {
382 } else {
385 goto fail;
386 }
388 }
390 }
391
395 }
397
398 fail:
399 if (ret)
402 }
403
405 {
413 if (*moof_size < 8 || *moof_size > size)
414 goto fail;
416 goto fail;
418 if (len > *moof_size)
419 goto fail;
421 goto fail;
425 goto fail;
430 if (len < 8 || len >= *moof_size)
431 goto fail;
432 if (tag ==
MKTAG(
'u',
'u',
'i',
'd')) {
433 static const uint8_t tfxd[] = {
434 0x6d, 0x1d, 0x9b, 0x05, 0x42, 0xd5, 0x44, 0xe6,
435 0x80, 0xe2, 0x14, 0x1d, 0xaf, 0xf7, 0x57, 0xb2
436 };
439 if (!memcmp(uuid, tfxd, 16) && len >= 8 + 16 + 4 + 16) {
443 ret = 0;
444 break;
445 }
446 }
448 }
449 fail:
452 }
453
455 {
456 int err;
464 return err;
465 }
466 }
468 if (!frag)
479 return 0;
480 }
481
483 {
491 }
492 while (size > 0) {
494 int n =
FFMIN(size,
sizeof(buf));
496 if (n <= 0) {
498 break;
499 }
502 }
507 }
508
510 {
513
516 char filename[1024], target_filename[1024], header_filename[1024];
518 int64_t start_ts,
duration, moof_size;
520 continue;
521
524 if (ret < 0)
525 break;
532
537 break;
540 copy_moof(s, filename, header_filename, moof_size);
541 rename(filename, target_filename);
542 add_fragment(os, target_filename, header_filename, start_ts, duration, start_pos,
size);
543 }
544
548 int j;
552 if (remove > 0) {
553 for (j = 0; j < remove; j++) {
557 }
560 }
563 }
564 }
565
566 if (ret >= 0)
569 }
570
572 {
578
581
586
590 }
591
594 }
595
597 {
600
602 char filename[1024];
604 unlink(filename);
606 }
607
609 return 0;
610 }
611
612 #define OFFSET(x) offsetof(SmoothStreamingContext, x)
613 #define E AV_OPT_FLAG_ENCODING_PARAM
615 {
"window_size",
"number of fragments kept in the manifest",
OFFSET(window_size),
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX,
E },
616 {
"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 },
617 {
"lookahead_count",
"number of lookahead fragments",
OFFSET(lookahead_count),
AV_OPT_TYPE_INT, { .i64 = 2 }, 0, INT_MAX,
E },
618 {
"min_frag_duration",
"minimum fragment duration (in microseconds)",
OFFSET(min_frag_duration),
AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX,
E },
619 {
"remove_at_exit",
"remove all fragments when finished",
OFFSET(remove_at_exit),
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1,
E },
620 { NULL },
621 };
622
628 };
629
630
632 .
name =
"smoothstreaming",
643 };