2 * Copyright (c) 2012 Martin Storsjo
4 * This file is part of FFmpeg.
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.
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.
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
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
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.
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.
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.
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.)
52 #include "libavformat/avformat.h"
53 #include "libavformat/os_support.h"
54 #include "libavutil/intreadwrite.h"
55 #include "libavutil/mathematics.h"
57 static int usage(const char *argv0
, int ret
)
59 fprintf(stderr
, "%s [-split] [-ismf] [-n basename] [-path-prefix prefix] "
60 "[-ismc-prefix prefix] [-output dir] file1 [file2] ...\n", argv0
);
75 int is_audio
, is_video
;
78 int sample_rate
, channels
;
79 uint8_t *codec_private
;
80 int codec_private_size
;
81 struct MoofOffset
*offsets
;
91 struct Track
**tracks
;
92 int video_track
, audio_track
;
93 int nb_video_tracks
, nb_audio_tracks
;
96 static int expect_tag(int32_t got_tag
, int32_t expected_tag
) {
97 if (got_tag
!= expected_tag
) {
98 char got_tag_str
[4], expected_tag_str
[4];
99 AV_WB32(got_tag_str
, got_tag
);
100 AV_WB32(expected_tag_str
, expected_tag
);
101 fprintf(stderr
, "wanted tag %.4s, got %.4s\n", expected_tag_str
,
108 static int copy_tag(AVIOContext
*in
, AVIOContext
*out
, int32_t tag_name
)
112 size
= avio_rb32(in
);
114 avio_wb32(out
, size
);
116 if (expect_tag(tag
, tag_name
) != 0)
121 int len
= FFMIN(sizeof(buf
), size
);
123 if ((got
= avio_read(in
, buf
, len
)) != len
) {
124 fprintf(stderr
, "short read, wanted %d, got %d\n", len
, got
);
127 avio_write(out
, buf
, len
);
133 static int skip_tag(AVIOContext
*in
, int32_t tag_name
)
135 int64_t pos
= avio_tell(in
);
138 size
= avio_rb32(in
);
140 if (expect_tag(tag
, tag_name
) != 0)
142 avio_seek(in
, pos
+ size
, SEEK_SET
);
146 static int write_fragment(const char *filename
, AVIOContext
*in
)
148 AVIOContext
*out
= NULL
;
151 if ((ret
= avio_open2(&out
, filename
, AVIO_FLAG_WRITE
, NULL
, NULL
)) < 0) {
153 av_strerror(ret
, errbuf
, sizeof(errbuf
));
154 fprintf(stderr
, "Unable to open %s: %s\n", filename
, errbuf
);
157 ret
= copy_tag(in
, out
, MKBETAG('m', 'o', 'o', 'f'));
159 ret
= copy_tag(in
, out
, MKBETAG('m', 'd', 'a', 't'));
167 static int skip_fragment(AVIOContext
*in
)
170 ret
= skip_tag(in
, MKBETAG('m', 'o', 'o', 'f'));
172 ret
= skip_tag(in
, MKBETAG('m', 'd', 'a', 't'));
176 static int write_fragments(struct Tracks
*tracks
, int start_index
,
177 AVIOContext
*in
, const char *basename
,
178 int split
, int ismf
, const char* output_prefix
)
180 char dirname
[2048], filename
[2048], idxname
[2048];
181 int i
, j
, ret
= 0, fragment_ret
;
185 snprintf(idxname
, sizeof(idxname
), "%s%s.ismf", output_prefix
, basename
);
186 out
= fopen(idxname
, "w");
188 ret
= AVERROR(errno
);
193 for (i
= start_index
; i
< tracks
->nb_tracks
; i
++) {
194 struct Track
*track
= tracks
->tracks
[i
];
195 const char *type
= track
->is_video
? "video" : "audio";
196 snprintf(dirname
, sizeof(dirname
), "%sQualityLevels(%d)", output_prefix
, track
->bitrate
);
198 if (mkdir(dirname
, 0777) == -1 && errno
!= EEXIST
) {
199 ret
= AVERROR(errno
);
204 for (j
= 0; j
< track
->chunks
; j
++) {
205 snprintf(filename
, sizeof(filename
), "%s/Fragments(%s=%"PRId64
")",
206 dirname
, type
, track
->offsets
[j
].time
);
207 avio_seek(in
, track
->offsets
[j
].offset
, SEEK_SET
);
209 fprintf(out
, "%s %"PRId64
, filename
, avio_tell(in
));
211 fragment_ret
= write_fragment(filename
, in
);
213 fragment_ret
= skip_fragment(in
);
215 fprintf(out
, " %"PRId64
"\n", avio_tell(in
));
216 if (fragment_ret
!= 0) {
217 fprintf(stderr
, "failed fragment %d in track %d (%s)\n", j
,
218 track
->track_id
, track
->name
);
229 static int read_tfra(struct Tracks
*tracks
, int start_index
, AVIOContext
*f
)
231 int ret
= AVERROR_EOF
, track_id
;
232 int version
, fieldlength
, i
, j
;
233 int64_t pos
= avio_tell(f
);
234 uint32_t size
= avio_rb32(f
);
235 struct Track
*track
= NULL
;
237 if (avio_rb32(f
) != MKBETAG('t', 'f', 'r', 'a'))
239 version
= avio_r8(f
);
241 track_id
= avio_rb32(f
); /* track id */
242 for (i
= start_index
; i
< tracks
->nb_tracks
&& !track
; i
++)
243 if (tracks
->tracks
[i
]->track_id
== track_id
)
244 track
= tracks
->tracks
[i
];
246 /* Ok, continue parsing the next atom */
250 fieldlength
= avio_rb32(f
);
251 track
->chunks
= avio_rb32(f
);
252 track
->offsets
= av_mallocz_array(track
->chunks
, sizeof(*track
->offsets
));
253 if (!track
->offsets
) {
254 ret
= AVERROR(ENOMEM
);
257 for (i
= 0; i
< track
->chunks
; i
++) {
259 track
->offsets
[i
].time
= avio_rb64(f
);
260 track
->offsets
[i
].offset
= avio_rb64(f
);
262 track
->offsets
[i
].time
= avio_rb32(f
);
263 track
->offsets
[i
].offset
= avio_rb32(f
);
265 for (j
= 0; j
< ((fieldlength
>> 4) & 3) + 1; j
++)
267 for (j
= 0; j
< ((fieldlength
>> 2) & 3) + 1; j
++)
269 for (j
= 0; j
< ((fieldlength
>> 0) & 3) + 1; j
++)
272 track
->offsets
[i
- 1].duration
= track
->offsets
[i
].time
-
273 track
->offsets
[i
- 1].time
;
275 if (track
->chunks
> 0)
276 track
->offsets
[track
->chunks
- 1].duration
= track
->duration
-
277 track
->offsets
[track
->chunks
- 1].time
;
281 avio_seek(f
, pos
+ size
, SEEK_SET
);
285 static int read_mfra(struct Tracks
*tracks
, int start_index
,
286 const char *file
, int split
, int ismf
,
287 const char *basename
, const char* output_prefix
)
290 const char* err_str
= "";
291 AVIOContext
*f
= NULL
;
294 if ((err
= avio_open2(&f
, file
, AVIO_FLAG_READ
, NULL
, NULL
)) < 0)
296 avio_seek(f
, avio_size(f
) - 4, SEEK_SET
);
297 mfra_size
= avio_rb32(f
);
298 avio_seek(f
, -mfra_size
, SEEK_CUR
);
299 if (avio_rb32(f
) != mfra_size
) {
300 err
= AVERROR_INVALIDDATA
;
301 err_str
= "mfra size mismatch";
304 if (avio_rb32(f
) != MKBETAG('m', 'f', 'r', 'a')) {
305 err
= AVERROR_INVALIDDATA
;
306 err_str
= "mfra tag mismatch";
309 while (!read_tfra(tracks
, start_index
, f
)) {
314 err
= write_fragments(tracks
, start_index
, f
, basename
, split
, ismf
,
316 err_str
= "error in write_fragments";
322 fprintf(stderr
, "Unable to read the MFRA atom in %s (%s)\n", file
, err_str
);
326 static int get_private_data(struct Track
*track
, AVCodecContext
*codec
)
328 track
->codec_private_size
= codec
->extradata_size
;
329 track
->codec_private
= av_mallocz(codec
->extradata_size
);
330 if (!track
->codec_private
)
331 return AVERROR(ENOMEM
);
332 memcpy(track
->codec_private
, codec
->extradata
, codec
->extradata_size
);
336 static int get_video_private_data(struct Track
*track
, AVCodecContext
*codec
)
338 AVIOContext
*io
= NULL
;
339 uint16_t sps_size
, pps_size
;
342 if (codec
->codec_id
== AV_CODEC_ID_VC1
)
343 return get_private_data(track
, codec
);
345 if ((err
= avio_open_dyn_buf(&io
)) < 0)
347 err
= AVERROR(EINVAL
);
348 if (codec
->extradata_size
< 11 || codec
->extradata
[0] != 1)
350 sps_size
= AV_RB16(&codec
->extradata
[6]);
351 if (11 + sps_size
> codec
->extradata_size
)
353 avio_wb32(io
, 0x00000001);
354 avio_write(io
, &codec
->extradata
[8], sps_size
);
355 pps_size
= AV_RB16(&codec
->extradata
[9 + sps_size
]);
356 if (11 + sps_size
+ pps_size
> codec
->extradata_size
)
358 avio_wb32(io
, 0x00000001);
359 avio_write(io
, &codec
->extradata
[11 + sps_size
], pps_size
);
363 track
->codec_private_size
= avio_close_dyn_buf(io
, &track
->codec_private
);
367 static int handle_file(struct Tracks
*tracks
, const char *file
, int split
,
368 int ismf
, const char *basename
,
369 const char* output_prefix
)
371 AVFormatContext
*ctx
= NULL
;
372 int err
= 0, i
, orig_tracks
= tracks
->nb_tracks
;
373 char errbuf
[50], *ptr
;
376 err
= avformat_open_input(&ctx
, file
, NULL
, NULL
);
378 av_strerror(err
, errbuf
, sizeof(errbuf
));
379 fprintf(stderr
, "Unable to open %s: %s\n", file
, errbuf
);
383 err
= avformat_find_stream_info(ctx
, NULL
);
385 av_strerror(err
, errbuf
, sizeof(errbuf
));
386 fprintf(stderr
, "Unable to identify %s: %s\n", file
, errbuf
);
390 if (ctx
->nb_streams
< 1) {
391 fprintf(stderr
, "No streams found in %s\n", file
);
395 for (i
= 0; i
< ctx
->nb_streams
; i
++) {
397 AVStream
*st
= ctx
->streams
[i
];
399 if (st
->codec
->bit_rate
== 0) {
400 fprintf(stderr
, "Skipping track %d in %s as it has zero bitrate\n",
405 track
= av_mallocz(sizeof(*track
));
407 err
= AVERROR(ENOMEM
);
410 temp
= av_realloc(tracks
->tracks
,
411 sizeof(*tracks
->tracks
) * (tracks
->nb_tracks
+ 1));
414 err
= AVERROR(ENOMEM
);
417 tracks
->tracks
= temp
;
418 tracks
->tracks
[tracks
->nb_tracks
] = track
;
421 if ((ptr
= strrchr(file
, '/')))
422 track
->name
= ptr
+ 1;
424 track
->bitrate
= st
->codec
->bit_rate
;
425 track
->track_id
= st
->id
;
426 track
->timescale
= st
->time_base
.den
;
427 track
->duration
= st
->duration
;
428 track
->is_audio
= st
->codec
->codec_type
== AVMEDIA_TYPE_AUDIO
;
429 track
->is_video
= st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
;
431 if (!track
->is_audio
&& !track
->is_video
) {
433 "Track %d in %s is neither video nor audio, skipping\n",
434 track
->track_id
, file
);
435 av_freep(&tracks
->tracks
[tracks
->nb_tracks
]);
439 tracks
->duration
= FFMAX(tracks
->duration
,
440 av_rescale_rnd(track
->duration
, AV_TIME_BASE
,
441 track
->timescale
, AV_ROUND_UP
));
443 if (track
->is_audio
) {
444 if (tracks
->audio_track
< 0)
445 tracks
->audio_track
= tracks
->nb_tracks
;
446 tracks
->nb_audio_tracks
++;
447 track
->channels
= st
->codec
->channels
;
448 track
->sample_rate
= st
->codec
->sample_rate
;
449 if (st
->codec
->codec_id
== AV_CODEC_ID_AAC
) {
450 track
->fourcc
= "AACL";
452 track
->blocksize
= 4;
453 } else if (st
->codec
->codec_id
== AV_CODEC_ID_WMAPRO
) {
454 track
->fourcc
= "WMAP";
455 track
->tag
= st
->codec
->codec_tag
;
456 track
->blocksize
= st
->codec
->block_align
;
458 get_private_data(track
, st
->codec
);
460 if (track
->is_video
) {
461 if (tracks
->video_track
< 0)
462 tracks
->video_track
= tracks
->nb_tracks
;
463 tracks
->nb_video_tracks
++;
464 track
->width
= st
->codec
->width
;
465 track
->height
= st
->codec
->height
;
466 if (st
->codec
->codec_id
== AV_CODEC_ID_H264
)
467 track
->fourcc
= "H264";
468 else if (st
->codec
->codec_id
== AV_CODEC_ID_VC1
)
469 track
->fourcc
= "WVC1";
470 get_video_private_data(track
, st
->codec
);
476 avformat_close_input(&ctx
);
478 err
= read_mfra(tracks
, orig_tracks
, file
, split
, ismf
, basename
,
483 avformat_close_input(&ctx
);
487 static void output_server_manifest(struct Tracks
*tracks
, const char *basename
,
488 const char *output_prefix
,
489 const char *path_prefix
,
490 const char *ismc_prefix
)
496 snprintf(filename
, sizeof(filename
), "%s%s.ism", output_prefix
, basename
);
497 out
= fopen(filename
, "w");
502 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
503 fprintf(out
, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
504 fprintf(out
, "\t<head>\n");
505 fprintf(out
, "\t\t<meta name=\"clientManifestRelativePath\" "
506 "content=\"%s%s.ismc\" />\n", ismc_prefix
, basename
);
507 fprintf(out
, "\t</head>\n");
508 fprintf(out
, "\t<body>\n");
509 fprintf(out
, "\t\t<switch>\n");
510 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
511 struct Track
*track
= tracks
->tracks
[i
];
512 const char *type
= track
->is_video
? "video" : "audio";
513 fprintf(out
, "\t\t\t<%s src=\"%s%s\" systemBitrate=\"%d\">\n",
514 type
, path_prefix
, track
->name
, track
->bitrate
);
515 fprintf(out
, "\t\t\t\t<param name=\"trackID\" value=\"%d\" "
516 "valueType=\"data\" />\n", track
->track_id
);
517 fprintf(out
, "\t\t\t</%s>\n", type
);
519 fprintf(out
, "\t\t</switch>\n");
520 fprintf(out
, "\t</body>\n");
521 fprintf(out
, "</smil>\n");
525 static void print_track_chunks(FILE *out
, struct Tracks
*tracks
, int main
,
529 struct Track
*track
= tracks
->tracks
[main
];
530 int should_print_time_mismatch
= 1;
532 for (i
= 0; i
< track
->chunks
; i
++) {
533 for (j
= main
+ 1; j
< tracks
->nb_tracks
; j
++) {
534 if (tracks
->tracks
[j
]->is_audio
== track
->is_audio
) {
535 if (track
->offsets
[i
].duration
!= tracks
->tracks
[j
]->offsets
[i
].duration
) {
536 fprintf(stderr
, "Mismatched duration of %s chunk %d in %s (%d) and %s (%d)\n",
537 type
, i
, track
->name
, main
, tracks
->tracks
[j
]->name
, j
);
538 should_print_time_mismatch
= 1;
540 if (track
->offsets
[i
].time
!= tracks
->tracks
[j
]->offsets
[i
].time
) {
541 if (should_print_time_mismatch
)
542 fprintf(stderr
, "Mismatched (start) time of %s chunk %d in %s (%d) and %s (%d)\n",
543 type
, i
, track
->name
, main
, tracks
->tracks
[j
]->name
, j
);
544 should_print_time_mismatch
= 0;
548 fprintf(out
, "\t\t<c n=\"%d\" d=\"%"PRId64
"\" />\n",
549 i
, track
->offsets
[i
].duration
);
553 static void output_client_manifest(struct Tracks
*tracks
, const char *basename
,
554 const char *output_prefix
, int split
)
561 snprintf(filename
, sizeof(filename
), "%sManifest", output_prefix
);
563 snprintf(filename
, sizeof(filename
), "%s%s.ismc", output_prefix
, basename
);
564 out
= fopen(filename
, "w");
569 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
570 fprintf(out
, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" "
571 "Duration=\"%"PRId64
"\">\n", tracks
->duration
* 10);
572 if (tracks
->video_track
>= 0) {
573 struct Track
*track
= tracks
->tracks
[tracks
->video_track
];
574 struct Track
*first_track
= track
;
577 "\t<StreamIndex Type=\"video\" QualityLevels=\"%d\" "
579 "Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n",
580 tracks
->nb_video_tracks
, track
->chunks
);
581 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
582 track
= tracks
->tracks
[i
];
583 if (!track
->is_video
)
586 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
587 "FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" "
588 "CodecPrivateData=\"",
589 index
, track
->bitrate
, track
->fourcc
, track
->width
, track
->height
);
590 for (j
= 0; j
< track
->codec_private_size
; j
++)
591 fprintf(out
, "%02X", track
->codec_private
[j
]);
592 fprintf(out
, "\" />\n");
594 if (track
->chunks
!= first_track
->chunks
)
595 fprintf(stderr
, "Mismatched number of video chunks in %s (id: %d, chunks %d) and %s (id: %d, chunks %d)\n",
596 track
->name
, track
->track_id
, track
->chunks
, first_track
->name
, first_track
->track_id
, first_track
->chunks
);
598 print_track_chunks(out
, tracks
, tracks
->video_track
, "video");
599 fprintf(out
, "\t</StreamIndex>\n");
601 if (tracks
->audio_track
>= 0) {
602 struct Track
*track
= tracks
->tracks
[tracks
->audio_track
];
603 struct Track
*first_track
= track
;
606 "\t<StreamIndex Type=\"audio\" QualityLevels=\"%d\" "
608 "Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n",
609 tracks
->nb_audio_tracks
, track
->chunks
);
610 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
611 track
= tracks
->tracks
[i
];
612 if (!track
->is_audio
)
615 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
616 "FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" "
617 "BitsPerSample=\"16\" PacketSize=\"%d\" "
618 "AudioTag=\"%d\" CodecPrivateData=\"",
619 index
, track
->bitrate
, track
->fourcc
, track
->sample_rate
,
620 track
->channels
, track
->blocksize
, track
->tag
);
621 for (j
= 0; j
< track
->codec_private_size
; j
++)
622 fprintf(out
, "%02X", track
->codec_private
[j
]);
623 fprintf(out
, "\" />\n");
625 if (track
->chunks
!= first_track
->chunks
)
626 fprintf(stderr
, "Mismatched number of audio chunks in %s and %s\n",
627 track
->name
, first_track
->name
);
629 print_track_chunks(out
, tracks
, tracks
->audio_track
, "audio");
630 fprintf(out
, "\t</StreamIndex>\n");
632 fprintf(out
, "</SmoothStreamingMedia>\n");
636 static void clean_tracks(struct Tracks
*tracks
)
639 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
640 av_freep(&tracks
->tracks
[i
]->codec_private
);
641 av_freep(&tracks
->tracks
[i
]->offsets
);
642 av_freep(&tracks
->tracks
[i
]);
644 av_freep(&tracks
->tracks
);
645 tracks
->nb_tracks
= 0;
648 int main(int argc
, char **argv
)
650 const char *basename
= NULL
;
651 const char *path_prefix
= "", *ismc_prefix
= "";
652 const char *output_prefix
= "";
653 char output_prefix_buf
[2048];
654 int split
= 0, ismf
= 0, i
;
655 struct Tracks tracks
= { 0, .video_track
= -1, .audio_track
= -1 };
659 for (i
= 1; i
< argc
; i
++) {
660 if (!strcmp(argv
[i
], "-n")) {
661 basename
= argv
[i
+ 1];
663 } else if (!strcmp(argv
[i
], "-path-prefix")) {
664 path_prefix
= argv
[i
+ 1];
666 } else if (!strcmp(argv
[i
], "-ismc-prefix")) {
667 ismc_prefix
= argv
[i
+ 1];
669 } else if (!strcmp(argv
[i
], "-output")) {
670 output_prefix
= argv
[i
+ 1];
672 if (output_prefix
[strlen(output_prefix
) - 1] != '/') {
673 snprintf(output_prefix_buf
, sizeof(output_prefix_buf
),
674 "%s/", output_prefix
);
675 output_prefix
= output_prefix_buf
;
677 } else if (!strcmp(argv
[i
], "-split")) {
679 } else if (!strcmp(argv
[i
], "-ismf")) {
681 } else if (argv
[i
][0] == '-') {
682 return usage(argv
[0], 1);
686 if (handle_file(&tracks
, argv
[i
], split
, ismf
,
687 basename
, output_prefix
))
691 if (!tracks
.nb_tracks
|| (!basename
&& !split
))
692 return usage(argv
[0], 1);
695 output_server_manifest(&tracks
, basename
, output_prefix
,
696 path_prefix
, ismc_prefix
);
697 output_client_manifest(&tracks
, basename
, output_prefix
, split
);
699 clean_tracks(&tracks
);