1 /*
2 * Copyright (c) 2012 Martin Storsjo
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg 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 * FFmpeg 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 FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /*
22 * To create a simple file for smooth streaming:
23 * ffmpeg <normal input/transcoding options> -movflags frag_keyframe foo.ismv
24 * ismindex -n foo foo.ismv
25 * This step creates foo.ism and foo.ismc that is required by IIS for
26 * serving it.
27 *
28 * With -ismf, it also creates foo.ismf, which maps fragment names to
29 * start-end offsets in the ismv, for use in your own streaming server.
30 *
31 * By adding -path-prefix path/, the produced foo.ism will refer to the
32 * files foo.ismv as "path/foo.ismv" - the prefix for the generated ismc
33 * file can be set with the -ismc-prefix option similarly.
34 *
35 * To pre-split files for serving as static files by a web server without
36 * any extra server support, create the ismv file as above, and split it:
37 * ismindex -split foo.ismv
38 * This step creates a file Manifest and directories QualityLevel(...),
39 * that can be read directly by a smooth streaming player.
40 *
41 * The -output dir option can be used to request that output files
42 * (both .ism/.ismc, or Manifest/QualityLevels* when splitting)
43 * should be written to this directory instead of in the current directory.
44 * (The directory itself isn't created if it doesn't already exist.)
45 */
46
47 #include <stdio.h>
48 #include <string.h>
49
56
58 {
59 fprintf(stderr, "%s [-split] [-ismf] [-n basename] [-path-prefix prefix] "
60 "[-ismc-prefix prefix] [-output dir] file1 [file2] ...\n", argv0);
62 }
63
68 };
69
86 };
87
94 };
95
97 if (got_tag != expected_tag) {
98 char got_tag_str[4], expected_tag_str[4];
100 AV_WB32(expected_tag_str, expected_tag);
101 fprintf(stderr, "wanted tag %.4s, got %.4s\n", expected_tag_str,
102 got_tag_str);
103 return -1;
104 }
105 return 0;
106 }
107
109 {
111
117 return -1;
120 char buf[1024];
122 int got;
124 fprintf(stderr,
"short read, wanted %d, got %d\n",
len, got);
125 break;
126 }
129 }
130 return 0;
131 }
132
134 {
137
141 return -1;
143 return 0;
144 }
145
147 {
150
152 char errbuf[100];
154 fprintf(stderr, "Unable to open %s: %s\n", filename, errbuf);
156 }
160
163
165 }
166
168 {
174 }
175
178 int split,
int ismf,
const char* output_prefix)
179 {
180 char dirname[2048], filename[2048], idxname[2048];
181 int i, j,
ret = 0, fragment_ret;
183
184 if (ismf) {
185 snprintf(idxname,
sizeof(idxname),
"%s%s.ismf", output_prefix, basename);
186 out = fopen(idxname,
"w");
189 perror(idxname);
191 }
192 }
196 snprintf(dirname,
sizeof(dirname),
"%sQualityLevels(%d)", output_prefix, track->
bitrate);
198 if (mkdir(dirname, 0777) == -1 && errno != EEXIST) {
200 perror(dirname);
202 }
203 }
204 for (j = 0; j < track->
chunks; j++) {
205 snprintf(filename,
sizeof(filename),
"%s/Fragments(%s=%"PRId64
")",
208 if (ismf)
212 else
214 if (ismf)
216 if (fragment_ret != 0) {
217 fprintf(stderr, "failed fragment %d in track %d (%s)\n", j,
220 }
221 }
222 }
227 }
228
231 {
235 int entries;
241 fprintf(stderr, "No sample duration in trun flags\n");
242 return -1;
243 }
245
248
250 for (
i = 0;
i < entries &&
pos < end;
i++) {
251 int sample_duration = default_duration;
257 if (sample_duration < 0) {
258 fprintf(stderr, "Negative sample duration %d\n", sample_duration);
259 return -1;
260 }
263 max_pts =
FFMAX(max_pts,
pts + sample_duration);
264 dts += sample_duration;
266 }
267
268 return max_pts - first_pts;
269 }
270
272 {
276 int default_duration = 0;
277
290 while (
pos < traf_pos + traf_size) {
305 }
309 }
311 }
312 fprintf(stderr, "Couldn't find trun\n");
314 }
316 }
317 fprintf(stderr, "Couldn't find traf\n");
318
321 }
322
324 {
330
336 for (
i = start_index;
i < tracks->
nb_tracks && !track;
i++)
339 if (!track) {
340 /* Ok, continue parsing the next atom */
343 }
351 }
352 // The duration here is always the difference between consecutive
353 // start times.
358 } else {
361 }
362 for (j = 0; j < ((fieldlength >> 4) & 3) + 1; j++)
364 for (j = 0; j < ((fieldlength >> 2) & 3) + 1; j++)
366 for (j = 0; j < ((fieldlength >> 0) & 3) + 1; j++)
371 }
376 }
377 // Now try to read the actual durations from the trun sample data.
381 // 3 allows for integer duration to drift a few units,
382 // e.g., for 1/3 durations
384 }
385 }
388 fprintf(stderr, "Calculated last chunk duration for track %d "
389 "was non-positive (%"PRId64"), probably due to missing "
395 } else {
397 }
398 fprintf(stderr, "corrected to %"PRId64"\n",
403 fprintf(stderr, "Track duration corrected to %"PRId64"\n",
405 }
406 }
408
412 }
413
415 const char *file,
int split,
int ismf,
416 const char *basename, const char* output_prefix)
417 {
418 int err = 0;
419 const char* err_str = "";
422
430 err_str = "mfra size mismatch";
432 }
435 err_str = "mfra tag mismatch";
437 }
439 /* Empty */
440 }
441
444 output_prefix);
445 err_str = "error in write_fragments";
446
450 if (err)
451 fprintf(stderr, "Unable to read the MFRA atom in %s (%s)\n", file, err_str);
452 return err;
453 }
454
456 {
463 return 0;
464 }
465
467 {
469 uint16_t sps_size, pps_size;
470 int err;
471
474
490 err = 0;
491
494 return err;
495 }
496
498 int ismf, const char *basename,
499 const char* output_prefix)
500 {
502 int err = 0,
i, orig_tracks = tracks->
nb_tracks;
503 char errbuf[50], *ptr;
505
507 if (err < 0) {
509 fprintf(stderr, "Unable to open %s: %s\n", file, errbuf);
510 return 1;
511 }
512
514 if (err < 0) {
516 fprintf(stderr, "Unable to identify %s: %s\n", file, errbuf);
518 }
519
521 fprintf(stderr, "No streams found in %s\n", file);
523 }
524
528
530 fprintf(stderr, "Skipping track %d in %s as it has zero bitrate\n",
532 continue;
533 }
534
536 if (!track) {
539 }
547 }
550
552 if ((ptr = strrchr(file, '/')))
553 track->
name = ptr + 1;
554
561
563 fprintf(stderr,
564 "Track %d in %s is neither video nor audio, skipping\n",
567 continue;
568 }
569
573
588 }
590 }
602 }
603
605 }
606
608
610 output_prefix);
611
615 return err;
616 }
617
619 const char *output_prefix,
620 const char *path_prefix,
621 const char *ismc_prefix)
622 {
623 char filename[1000];
626
627 snprintf(filename,
sizeof(filename),
"%s%s.ism", output_prefix, basename);
628 out = fopen(filename,
"w");
630 perror(filename);
631 return;
632 }
633 fprintf(
out,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
634 fprintf(
out,
"<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
635 fprintf(
out,
"\t<head>\n");
636 fprintf(
out,
"\t\t<meta name=\"clientManifestRelativePath\" "
637 "content=\"%s%s.ismc\" />\n", ismc_prefix, basename);
638 fprintf(
out,
"\t</head>\n");
639 fprintf(
out,
"\t<body>\n");
640 fprintf(
out,
"\t\t<switch>\n");
644 fprintf(
out,
"\t\t\t<%s src=\"%s%s\" systemBitrate=\"%d\">\n",
646 fprintf(
out,
"\t\t\t\t<param name=\"trackID\" value=\"%d\" "
647 "valueType=\"data\" />\n", track->
track_id);
648 fprintf(
out,
"\t\t\t</%s>\n",
type);
649 }
650 fprintf(
out,
"\t\t</switch>\n");
651 fprintf(
out,
"\t</body>\n");
652 fprintf(
out,
"</smil>\n");
654 }
655
658 {
662 int should_print_time_mismatch = 1;
663
668 fprintf(stderr, "Mismatched duration of %s chunk %d in %s (%d) and %s (%d)\n",
670 should_print_time_mismatch = 1;
671 }
673 if (should_print_time_mismatch)
674 fprintf(stderr, "Mismatched (start) time of %s chunk %d in %s (%d) and %s (%d)\n",
676 should_print_time_mismatch = 0;
677 }
678 }
679 }
680 fprintf(
out,
"\t\t<c n=\"%d\" d=\"%"PRId64
"\" ",
685 }
687 fprintf(
out,
"/>\n");
688 }
689 }
690
692 const char *output_prefix,
int split)
693 {
694 char filename[1000];
697
699 snprintf(filename,
sizeof(filename),
"%sManifest", output_prefix);
700 else
701 snprintf(filename,
sizeof(filename),
"%s%s.ismc", output_prefix, basename);
702 out = fopen(filename,
"w");
704 perror(filename);
705 return;
706 }
707 fprintf(
out,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
708 fprintf(
out,
"<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" "
709 "Duration=\"%"PRId64
"\">\n", tracks->
duration * 10);
712 struct Track *first_track = track;
715 "\t<StreamIndex Type=\"video\" QualityLevels=\"%d\" "
716 "Chunks=\"%d\" "
717 "Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n",
722 continue;
724 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
725 "FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" "
726 "CodecPrivateData=\"",
730 fprintf(
out,
"\" />\n");
733 fprintf(stderr, "Mismatched number of video chunks in %s (id: %d, chunks %d) and %s (id: %d, chunks %d)\n",
735 }
737 fprintf(
out,
"\t</StreamIndex>\n");
738 }
741 struct Track *first_track = track;
744 "\t<StreamIndex Type=\"audio\" QualityLevels=\"%d\" "
745 "Chunks=\"%d\" "
746 "Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n",
751 continue;
753 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
754 "FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" "
755 "BitsPerSample=\"16\" PacketSize=\"%d\" "
756 "AudioTag=\"%d\" CodecPrivateData=\"",
761 fprintf(
out,
"\" />\n");
764 fprintf(stderr, "Mismatched number of audio chunks in %s and %s\n",
766 }
768 fprintf(
out,
"\t</StreamIndex>\n");
769 }
770 fprintf(
out,
"</SmoothStreamingMedia>\n");
772 }
773
775 {
781 }
784 }
785
786 int main(
int argc,
char **argv)
787 {
788 const char *basename =
NULL;
789 const char *path_prefix = "", *ismc_prefix = "";
790 const char *output_prefix = "";
791 char output_prefix_buf[2048];
792 int split = 0, ismf = 0,
i;
793 struct Tracks tracks = { 0, .video_track = -1, .audio_track = -1 };
794
795 for (
i = 1;
i < argc;
i++) {
796 if (!strcmp(argv[
i],
"-n")) {
797 basename = argv[
i + 1];
799 }
else if (!strcmp(argv[
i],
"-path-prefix")) {
800 path_prefix = argv[
i + 1];
802 }
else if (!strcmp(argv[
i],
"-ismc-prefix")) {
803 ismc_prefix = argv[
i + 1];
805 }
else if (!strcmp(argv[
i],
"-output")) {
806 output_prefix = argv[
i + 1];
808 if (output_prefix[strlen(output_prefix) - 1] != '/') {
809 snprintf(output_prefix_buf,
sizeof(output_prefix_buf),
810 "%s/", output_prefix);
811 output_prefix = output_prefix_buf;
812 }
813 }
else if (!strcmp(argv[
i],
"-split")) {
815 }
else if (!strcmp(argv[
i],
"-ismf")) {
816 ismf = 1;
817 }
else if (argv[
i][0] ==
'-') {
818 return usage(argv[0], 1);
819 } else {
820 if (!basename)
821 ismf = 0;
823 basename, output_prefix))
824 return 1;
825 }
826 }
828 return usage(argv[0], 1);
829
832 path_prefix, ismc_prefix);
834
836
837 return 0;
838 }