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/isom.h"
54 #include "libavformat/os_support.h"
55 #include "libavutil/intreadwrite.h"
56 #include "libavutil/mathematics.h"
58 static int usage(const char *argv0
, int ret
)
60 fprintf(stderr
, "%s [-split] [-ismf] [-n basename] [-path-prefix prefix] "
61 "[-ismc-prefix prefix] [-output dir] file1 [file2] ...\n", argv0
);
76 int is_audio
, is_video
;
79 int sample_rate
, channels
;
80 uint8_t *codec_private
;
81 int codec_private_size
;
82 struct MoofOffset
*offsets
;
92 struct Track
**tracks
;
93 int video_track
, audio_track
;
94 int nb_video_tracks
, nb_audio_tracks
;
97 static int expect_tag(int32_t got_tag
, int32_t expected_tag
) {
98 if (got_tag
!= expected_tag
) {
99 char got_tag_str
[4], expected_tag_str
[4];
100 AV_WB32(got_tag_str
, got_tag
);
101 AV_WB32(expected_tag_str
, expected_tag
);
102 fprintf(stderr
, "wanted tag %.4s, got %.4s\n", expected_tag_str
,
109 static int copy_tag(AVIOContext
*in
, AVIOContext
*out
, int32_t tag_name
)
113 size
= avio_rb32(in
);
115 avio_wb32(out
, size
);
117 if (expect_tag(tag
, tag_name
) != 0)
122 int len
= FFMIN(sizeof(buf
), size
);
124 if ((got
= avio_read(in
, buf
, len
)) != len
) {
125 fprintf(stderr
, "short read, wanted %d, got %d\n", len
, got
);
128 avio_write(out
, buf
, len
);
134 static int skip_tag(AVIOContext
*in
, int32_t tag_name
)
136 int64_t pos
= avio_tell(in
);
139 size
= avio_rb32(in
);
141 if (expect_tag(tag
, tag_name
) != 0)
143 avio_seek(in
, pos
+ size
, SEEK_SET
);
147 static int write_fragment(const char *filename
, AVIOContext
*in
)
149 AVIOContext
*out
= NULL
;
152 if ((ret
= avio_open2(&out
, filename
, AVIO_FLAG_WRITE
, NULL
, NULL
)) < 0) {
154 av_strerror(ret
, errbuf
, sizeof(errbuf
));
155 fprintf(stderr
, "Unable to open %s: %s\n", filename
, errbuf
);
158 ret
= copy_tag(in
, out
, MKBETAG('m', 'o', 'o', 'f'));
160 ret
= copy_tag(in
, out
, MKBETAG('m', 'd', 'a', 't'));
168 static int skip_fragment(AVIOContext
*in
)
171 ret
= skip_tag(in
, MKBETAG('m', 'o', 'o', 'f'));
173 ret
= skip_tag(in
, MKBETAG('m', 'd', 'a', 't'));
177 static int write_fragments(struct Tracks
*tracks
, int start_index
,
178 AVIOContext
*in
, const char *basename
,
179 int split
, int ismf
, const char* output_prefix
)
181 char dirname
[2048], filename
[2048], idxname
[2048];
182 int i
, j
, ret
= 0, fragment_ret
;
186 snprintf(idxname
, sizeof(idxname
), "%s%s.ismf", output_prefix
, basename
);
187 out
= fopen(idxname
, "w");
189 ret
= AVERROR(errno
);
194 for (i
= start_index
; i
< tracks
->nb_tracks
; i
++) {
195 struct Track
*track
= tracks
->tracks
[i
];
196 const char *type
= track
->is_video
? "video" : "audio";
197 snprintf(dirname
, sizeof(dirname
), "%sQualityLevels(%d)", output_prefix
, track
->bitrate
);
199 if (mkdir(dirname
, 0777) == -1 && errno
!= EEXIST
) {
200 ret
= AVERROR(errno
);
205 for (j
= 0; j
< track
->chunks
; j
++) {
206 snprintf(filename
, sizeof(filename
), "%s/Fragments(%s=%"PRId64
")",
207 dirname
, type
, track
->offsets
[j
].time
);
208 avio_seek(in
, track
->offsets
[j
].offset
, SEEK_SET
);
210 fprintf(out
, "%s %"PRId64
, filename
, avio_tell(in
));
212 fragment_ret
= write_fragment(filename
, in
);
214 fragment_ret
= skip_fragment(in
);
216 fprintf(out
, " %"PRId64
"\n", avio_tell(in
));
217 if (fragment_ret
!= 0) {
218 fprintf(stderr
, "failed fragment %d in track %d (%s)\n", j
,
219 track
->track_id
, track
->name
);
230 static int64_t read_trun_duration(AVIOContext
*in
, int default_duration
,
237 int64_t first_pts
= 0;
239 avio_r8(in
); /* version */
240 flags
= avio_rb24(in
);
241 if (default_duration
<= 0 && !(flags
& MOV_TRUN_SAMPLE_DURATION
)) {
242 fprintf(stderr
, "No sample duration in trun flags\n");
245 entries
= avio_rb32(in
);
247 if (flags
& MOV_TRUN_DATA_OFFSET
) avio_rb32(in
);
248 if (flags
& MOV_TRUN_FIRST_SAMPLE_FLAGS
) avio_rb32(in
);
251 for (i
= 0; i
< entries
&& pos
< end
; i
++) {
252 int sample_duration
= default_duration
;
254 if (flags
& MOV_TRUN_SAMPLE_DURATION
) sample_duration
= avio_rb32(in
);
255 if (flags
& MOV_TRUN_SAMPLE_SIZE
) avio_rb32(in
);
256 if (flags
& MOV_TRUN_SAMPLE_FLAGS
) avio_rb32(in
);
257 if (flags
& MOV_TRUN_SAMPLE_CTS
) pts
+= avio_rb32(in
);
258 if (sample_duration
< 0) {
259 fprintf(stderr
, "Negative sample duration %d\n", sample_duration
);
264 max_pts
= FFMAX(max_pts
, pts
+ sample_duration
);
265 dts
+= sample_duration
;
269 return max_pts
- first_pts
;
272 static int64_t read_moof_duration(AVIOContext
*in
, int64_t offset
)
275 int32_t moof_size
, size
, tag
;
277 int default_duration
= 0;
279 avio_seek(in
, offset
, SEEK_SET
);
280 moof_size
= avio_rb32(in
);
282 if (expect_tag(tag
, MKBETAG('m', 'o', 'o', 'f')) != 0)
284 while (pos
< offset
+ moof_size
) {
286 size
= avio_rb32(in
);
288 if (tag
== MKBETAG('t', 'r', 'a', 'f')) {
289 int64_t traf_pos
= pos
;
290 int64_t traf_size
= size
;
291 while (pos
< traf_pos
+ traf_size
) {
293 size
= avio_rb32(in
);
295 if (tag
== MKBETAG('t', 'f', 'h', 'd')) {
297 avio_r8(in
); /* version */
298 flags
= avio_rb24(in
);
299 avio_rb32(in
); /* track_id */
300 if (flags
& MOV_TFHD_BASE_DATA_OFFSET
)
302 if (flags
& MOV_TFHD_STSD_ID
)
304 if (flags
& MOV_TFHD_DEFAULT_DURATION
)
305 default_duration
= avio_rb32(in
);
307 if (tag
== MKBETAG('t', 'r', 'u', 'n')) {
308 return read_trun_duration(in
, default_duration
,
311 avio_seek(in
, pos
+ size
, SEEK_SET
);
313 fprintf(stderr
, "Couldn't find trun\n");
316 avio_seek(in
, pos
+ size
, SEEK_SET
);
318 fprintf(stderr
, "Couldn't find traf\n");
324 static int read_tfra(struct Tracks
*tracks
, int start_index
, AVIOContext
*f
)
326 int ret
= AVERROR_EOF
, track_id
;
327 int version
, fieldlength
, i
, j
;
328 int64_t pos
= avio_tell(f
);
329 uint32_t size
= avio_rb32(f
);
330 struct Track
*track
= NULL
;
332 if (avio_rb32(f
) != MKBETAG('t', 'f', 'r', 'a'))
334 version
= avio_r8(f
);
336 track_id
= avio_rb32(f
); /* track id */
337 for (i
= start_index
; i
< tracks
->nb_tracks
&& !track
; i
++)
338 if (tracks
->tracks
[i
]->track_id
== track_id
)
339 track
= tracks
->tracks
[i
];
341 /* Ok, continue parsing the next atom */
345 fieldlength
= avio_rb32(f
);
346 track
->chunks
= avio_rb32(f
);
347 track
->offsets
= av_mallocz_array(track
->chunks
, sizeof(*track
->offsets
));
348 if (!track
->offsets
) {
350 ret
= AVERROR(ENOMEM
);
353 // The duration here is always the difference between consecutive
355 for (i
= 0; i
< track
->chunks
; i
++) {
357 track
->offsets
[i
].time
= avio_rb64(f
);
358 track
->offsets
[i
].offset
= avio_rb64(f
);
360 track
->offsets
[i
].time
= avio_rb32(f
);
361 track
->offsets
[i
].offset
= avio_rb32(f
);
363 for (j
= 0; j
< ((fieldlength
>> 4) & 3) + 1; j
++)
365 for (j
= 0; j
< ((fieldlength
>> 2) & 3) + 1; j
++)
367 for (j
= 0; j
< ((fieldlength
>> 0) & 3) + 1; j
++)
370 track
->offsets
[i
- 1].duration
= track
->offsets
[i
].time
-
371 track
->offsets
[i
- 1].time
;
373 if (track
->chunks
> 0) {
374 track
->offsets
[track
->chunks
- 1].duration
= track
->offsets
[0].time
+
376 track
->offsets
[track
->chunks
- 1].time
;
378 // Now try to read the actual durations from the trun sample data.
379 for (i
= 0; i
< track
->chunks
; i
++) {
380 int64_t duration
= read_moof_duration(f
, track
->offsets
[i
].offset
);
381 if (duration
> 0 && llabs(duration
- track
->offsets
[i
].duration
) > 3) {
382 // 3 allows for integer duration to drift a few units,
383 // e.g., for 1/3 durations
384 track
->offsets
[i
].duration
= duration
;
387 if (track
->chunks
> 0) {
388 if (track
->offsets
[track
->chunks
- 1].duration
<= 0) {
389 fprintf(stderr
, "Calculated last chunk duration for track %d "
390 "was non-positive (%"PRId64
"), probably due to missing "
391 "fragments ", track
->track_id
,
392 track
->offsets
[track
->chunks
- 1].duration
);
393 if (track
->chunks
> 1) {
394 track
->offsets
[track
->chunks
- 1].duration
=
395 track
->offsets
[track
->chunks
- 2].duration
;
397 track
->offsets
[track
->chunks
- 1].duration
= 1;
399 fprintf(stderr
, "corrected to %"PRId64
"\n",
400 track
->offsets
[track
->chunks
- 1].duration
);
401 track
->duration
= track
->offsets
[track
->chunks
- 1].time
+
402 track
->offsets
[track
->chunks
- 1].duration
-
403 track
->offsets
[0].time
;
404 fprintf(stderr
, "Track duration corrected to %"PRId64
"\n",
411 avio_seek(f
, pos
+ size
, SEEK_SET
);
415 static int read_mfra(struct Tracks
*tracks
, int start_index
,
416 const char *file
, int split
, int ismf
,
417 const char *basename
, const char* output_prefix
)
420 const char* err_str
= "";
421 AVIOContext
*f
= NULL
;
424 if ((err
= avio_open2(&f
, file
, AVIO_FLAG_READ
, NULL
, NULL
)) < 0)
426 avio_seek(f
, avio_size(f
) - 4, SEEK_SET
);
427 mfra_size
= avio_rb32(f
);
428 avio_seek(f
, -mfra_size
, SEEK_CUR
);
429 if (avio_rb32(f
) != mfra_size
) {
430 err
= AVERROR_INVALIDDATA
;
431 err_str
= "mfra size mismatch";
434 if (avio_rb32(f
) != MKBETAG('m', 'f', 'r', 'a')) {
435 err
= AVERROR_INVALIDDATA
;
436 err_str
= "mfra tag mismatch";
439 while (!read_tfra(tracks
, start_index
, f
)) {
444 err
= write_fragments(tracks
, start_index
, f
, basename
, split
, ismf
,
446 err_str
= "error in write_fragments";
452 fprintf(stderr
, "Unable to read the MFRA atom in %s (%s)\n", file
, err_str
);
456 static int get_private_data(struct Track
*track
, AVCodecParameters
*codecpar
)
458 track
->codec_private_size
= 0;
459 track
->codec_private
= av_mallocz(codecpar
->extradata_size
);
460 if (!track
->codec_private
)
461 return AVERROR(ENOMEM
);
462 track
->codec_private_size
= codecpar
->extradata_size
;
463 memcpy(track
->codec_private
, codecpar
->extradata
, codecpar
->extradata_size
);
467 static int get_video_private_data(struct Track
*track
, AVCodecParameters
*codecpar
)
469 AVIOContext
*io
= NULL
;
470 uint16_t sps_size
, pps_size
;
473 if (codecpar
->codec_id
== AV_CODEC_ID_VC1
)
474 return get_private_data(track
, codecpar
);
476 if ((err
= avio_open_dyn_buf(&io
)) < 0)
478 err
= AVERROR(EINVAL
);
479 if (codecpar
->extradata_size
< 11 || codecpar
->extradata
[0] != 1)
481 sps_size
= AV_RB16(&codecpar
->extradata
[6]);
482 if (11 + sps_size
> codecpar
->extradata_size
)
484 avio_wb32(io
, 0x00000001);
485 avio_write(io
, &codecpar
->extradata
[8], sps_size
);
486 pps_size
= AV_RB16(&codecpar
->extradata
[9 + sps_size
]);
487 if (11 + sps_size
+ pps_size
> codecpar
->extradata_size
)
489 avio_wb32(io
, 0x00000001);
490 avio_write(io
, &codecpar
->extradata
[11 + sps_size
], pps_size
);
494 track
->codec_private_size
= avio_close_dyn_buf(io
, &track
->codec_private
);
498 static int handle_file(struct Tracks
*tracks
, const char *file
, int split
,
499 int ismf
, const char *basename
,
500 const char* output_prefix
)
502 AVFormatContext
*ctx
= NULL
;
503 int err
= 0, i
, orig_tracks
= tracks
->nb_tracks
;
504 char errbuf
[50], *ptr
;
507 err
= avformat_open_input(&ctx
, file
, NULL
, NULL
);
509 av_strerror(err
, errbuf
, sizeof(errbuf
));
510 fprintf(stderr
, "Unable to open %s: %s\n", file
, errbuf
);
514 err
= avformat_find_stream_info(ctx
, NULL
);
516 av_strerror(err
, errbuf
, sizeof(errbuf
));
517 fprintf(stderr
, "Unable to identify %s: %s\n", file
, errbuf
);
521 if (ctx
->nb_streams
< 1) {
522 fprintf(stderr
, "No streams found in %s\n", file
);
526 for (i
= 0; i
< ctx
->nb_streams
; i
++) {
528 AVStream
*st
= ctx
->streams
[i
];
530 if (st
->codecpar
->bit_rate
== 0) {
531 fprintf(stderr
, "Skipping track %d in %s as it has zero bitrate\n",
536 track
= av_mallocz(sizeof(*track
));
538 err
= AVERROR(ENOMEM
);
541 temp
= av_realloc_array(tracks
->tracks
,
542 tracks
->nb_tracks
+ 1,
543 sizeof(*tracks
->tracks
));
546 err
= AVERROR(ENOMEM
);
549 tracks
->tracks
= temp
;
550 tracks
->tracks
[tracks
->nb_tracks
] = track
;
553 if ((ptr
= strrchr(file
, '/')))
554 track
->name
= ptr
+ 1;
556 track
->bitrate
= st
->codecpar
->bit_rate
;
557 track
->track_id
= st
->id
;
558 track
->timescale
= st
->time_base
.den
;
559 track
->duration
= st
->duration
;
560 track
->is_audio
= st
->codecpar
->codec_type
== AVMEDIA_TYPE_AUDIO
;
561 track
->is_video
= st
->codecpar
->codec_type
== AVMEDIA_TYPE_VIDEO
;
563 if (!track
->is_audio
&& !track
->is_video
) {
565 "Track %d in %s is neither video nor audio, skipping\n",
566 track
->track_id
, file
);
567 av_freep(&tracks
->tracks
[tracks
->nb_tracks
]);
571 tracks
->duration
= FFMAX(tracks
->duration
,
572 av_rescale_rnd(track
->duration
, AV_TIME_BASE
,
573 track
->timescale
, AV_ROUND_UP
));
575 if (track
->is_audio
) {
576 if (tracks
->audio_track
< 0)
577 tracks
->audio_track
= tracks
->nb_tracks
;
578 tracks
->nb_audio_tracks
++;
579 track
->channels
= st
->codecpar
->channels
;
580 track
->sample_rate
= st
->codecpar
->sample_rate
;
581 if (st
->codecpar
->codec_id
== AV_CODEC_ID_AAC
) {
582 track
->fourcc
= "AACL";
584 track
->blocksize
= 4;
585 } else if (st
->codecpar
->codec_id
== AV_CODEC_ID_WMAPRO
) {
586 track
->fourcc
= "WMAP";
587 track
->tag
= st
->codecpar
->codec_tag
;
588 track
->blocksize
= st
->codecpar
->block_align
;
590 get_private_data(track
, st
->codecpar
);
592 if (track
->is_video
) {
593 if (tracks
->video_track
< 0)
594 tracks
->video_track
= tracks
->nb_tracks
;
595 tracks
->nb_video_tracks
++;
596 track
->width
= st
->codecpar
->width
;
597 track
->height
= st
->codecpar
->height
;
598 if (st
->codecpar
->codec_id
== AV_CODEC_ID_H264
)
599 track
->fourcc
= "H264";
600 else if (st
->codecpar
->codec_id
== AV_CODEC_ID_VC1
)
601 track
->fourcc
= "WVC1";
602 get_video_private_data(track
, st
->codecpar
);
608 avformat_close_input(&ctx
);
610 err
= read_mfra(tracks
, orig_tracks
, file
, split
, ismf
, basename
,
615 avformat_close_input(&ctx
);
619 static void output_server_manifest(struct Tracks
*tracks
, const char *basename
,
620 const char *output_prefix
,
621 const char *path_prefix
,
622 const char *ismc_prefix
)
628 snprintf(filename
, sizeof(filename
), "%s%s.ism", output_prefix
, basename
);
629 out
= fopen(filename
, "w");
634 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
635 fprintf(out
, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
636 fprintf(out
, "\t<head>\n");
637 fprintf(out
, "\t\t<meta name=\"clientManifestRelativePath\" "
638 "content=\"%s%s.ismc\" />\n", ismc_prefix
, basename
);
639 fprintf(out
, "\t</head>\n");
640 fprintf(out
, "\t<body>\n");
641 fprintf(out
, "\t\t<switch>\n");
642 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
643 struct Track
*track
= tracks
->tracks
[i
];
644 const char *type
= track
->is_video
? "video" : "audio";
645 fprintf(out
, "\t\t\t<%s src=\"%s%s\" systemBitrate=\"%d\">\n",
646 type
, path_prefix
, track
->name
, track
->bitrate
);
647 fprintf(out
, "\t\t\t\t<param name=\"trackID\" value=\"%d\" "
648 "valueType=\"data\" />\n", track
->track_id
);
649 fprintf(out
, "\t\t\t</%s>\n", type
);
651 fprintf(out
, "\t\t</switch>\n");
652 fprintf(out
, "\t</body>\n");
653 fprintf(out
, "</smil>\n");
657 static void print_track_chunks(FILE *out
, struct Tracks
*tracks
, int main
,
662 struct Track
*track
= tracks
->tracks
[main
];
663 int should_print_time_mismatch
= 1;
665 for (i
= 0; i
< track
->chunks
; i
++) {
666 for (j
= main
+ 1; j
< tracks
->nb_tracks
; j
++) {
667 if (tracks
->tracks
[j
]->is_audio
== track
->is_audio
) {
668 if (track
->offsets
[i
].duration
!= tracks
->tracks
[j
]->offsets
[i
].duration
) {
669 fprintf(stderr
, "Mismatched duration of %s chunk %d in %s (%d) and %s (%d)\n",
670 type
, i
, track
->name
, main
, tracks
->tracks
[j
]->name
, j
);
671 should_print_time_mismatch
= 1;
673 if (track
->offsets
[i
].time
!= tracks
->tracks
[j
]->offsets
[i
].time
) {
674 if (should_print_time_mismatch
)
675 fprintf(stderr
, "Mismatched (start) time of %s chunk %d in %s (%d) and %s (%d)\n",
676 type
, i
, track
->name
, main
, tracks
->tracks
[j
]->name
, j
);
677 should_print_time_mismatch
= 0;
681 fprintf(out
, "\t\t<c n=\"%d\" d=\"%"PRId64
"\" ",
682 i
, track
->offsets
[i
].duration
);
683 if (pos
!= track
->offsets
[i
].time
) {
684 fprintf(out
, "t=\"%"PRId64
"\" ", track
->offsets
[i
].time
);
685 pos
= track
->offsets
[i
].time
;
687 pos
+= track
->offsets
[i
].duration
;
688 fprintf(out
, "/>\n");
692 static void output_client_manifest(struct Tracks
*tracks
, const char *basename
,
693 const char *output_prefix
, int split
)
700 snprintf(filename
, sizeof(filename
), "%sManifest", output_prefix
);
702 snprintf(filename
, sizeof(filename
), "%s%s.ismc", output_prefix
, basename
);
703 out
= fopen(filename
, "w");
708 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
709 fprintf(out
, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" "
710 "Duration=\"%"PRId64
"\">\n", tracks
->duration
* 10);
711 if (tracks
->video_track
>= 0) {
712 struct Track
*track
= tracks
->tracks
[tracks
->video_track
];
713 struct Track
*first_track
= track
;
716 "\t<StreamIndex Type=\"video\" QualityLevels=\"%d\" "
718 "Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n",
719 tracks
->nb_video_tracks
, track
->chunks
);
720 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
721 track
= tracks
->tracks
[i
];
722 if (!track
->is_video
)
725 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
726 "FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" "
727 "CodecPrivateData=\"",
728 index
, track
->bitrate
, track
->fourcc
, track
->width
, track
->height
);
729 for (j
= 0; j
< track
->codec_private_size
; j
++)
730 fprintf(out
, "%02X", track
->codec_private
[j
]);
731 fprintf(out
, "\" />\n");
733 if (track
->chunks
!= first_track
->chunks
)
734 fprintf(stderr
, "Mismatched number of video chunks in %s (id: %d, chunks %d) and %s (id: %d, chunks %d)\n",
735 track
->name
, track
->track_id
, track
->chunks
, first_track
->name
, first_track
->track_id
, first_track
->chunks
);
737 print_track_chunks(out
, tracks
, tracks
->video_track
, "video");
738 fprintf(out
, "\t</StreamIndex>\n");
740 if (tracks
->audio_track
>= 0) {
741 struct Track
*track
= tracks
->tracks
[tracks
->audio_track
];
742 struct Track
*first_track
= track
;
745 "\t<StreamIndex Type=\"audio\" QualityLevels=\"%d\" "
747 "Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n",
748 tracks
->nb_audio_tracks
, track
->chunks
);
749 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
750 track
= tracks
->tracks
[i
];
751 if (!track
->is_audio
)
754 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
755 "FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" "
756 "BitsPerSample=\"16\" PacketSize=\"%d\" "
757 "AudioTag=\"%d\" CodecPrivateData=\"",
758 index
, track
->bitrate
, track
->fourcc
, track
->sample_rate
,
759 track
->channels
, track
->blocksize
, track
->tag
);
760 for (j
= 0; j
< track
->codec_private_size
; j
++)
761 fprintf(out
, "%02X", track
->codec_private
[j
]);
762 fprintf(out
, "\" />\n");
764 if (track
->chunks
!= first_track
->chunks
)
765 fprintf(stderr
, "Mismatched number of audio chunks in %s and %s\n",
766 track
->name
, first_track
->name
);
768 print_track_chunks(out
, tracks
, tracks
->audio_track
, "audio");
769 fprintf(out
, "\t</StreamIndex>\n");
771 fprintf(out
, "</SmoothStreamingMedia>\n");
775 static void clean_tracks(struct Tracks
*tracks
)
778 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
779 av_freep(&tracks
->tracks
[i
]->codec_private
);
780 av_freep(&tracks
->tracks
[i
]->offsets
);
781 av_freep(&tracks
->tracks
[i
]);
783 av_freep(&tracks
->tracks
);
784 tracks
->nb_tracks
= 0;
787 int main(int argc
, char **argv
)
789 const char *basename
= NULL
;
790 const char *path_prefix
= "", *ismc_prefix
= "";
791 const char *output_prefix
= "";
792 char output_prefix_buf
[2048];
793 int split
= 0, ismf
= 0, i
;
794 struct Tracks tracks
= { 0, .video_track
= -1, .audio_track
= -1 };
798 for (i
= 1; i
< argc
; i
++) {
799 if (!strcmp(argv
[i
], "-n")) {
800 basename
= argv
[i
+ 1];
802 } else if (!strcmp(argv
[i
], "-path-prefix")) {
803 path_prefix
= argv
[i
+ 1];
805 } else if (!strcmp(argv
[i
], "-ismc-prefix")) {
806 ismc_prefix
= argv
[i
+ 1];
808 } else if (!strcmp(argv
[i
], "-output")) {
809 output_prefix
= argv
[i
+ 1];
811 if (output_prefix
[strlen(output_prefix
) - 1] != '/') {
812 snprintf(output_prefix_buf
, sizeof(output_prefix_buf
),
813 "%s/", output_prefix
);
814 output_prefix
= output_prefix_buf
;
816 } else if (!strcmp(argv
[i
], "-split")) {
818 } else if (!strcmp(argv
[i
], "-ismf")) {
820 } else if (argv
[i
][0] == '-') {
821 return usage(argv
[0], 1);
825 if (handle_file(&tracks
, argv
[i
], split
, ismf
,
826 basename
, output_prefix
))
830 if (!tracks
.nb_tracks
|| (!basename
&& !split
))
831 return usage(argv
[0], 1);
834 output_server_manifest(&tracks
, basename
, output_prefix
,
835 path_prefix
, ismc_prefix
);
836 output_client_manifest(&tracks
, basename
, output_prefix
, split
);
838 clean_tracks(&tracks
);