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 avio_r8(in
); /* version */
238 flags
= avio_rb24(in
);
239 if (default_duration
<= 0 && !(flags
& MOV_TRUN_SAMPLE_DURATION
)) {
240 fprintf(stderr
, "No sample duration in trun flags\n");
243 entries
= avio_rb32(in
);
245 if (flags
& MOV_TRUN_DATA_OFFSET
) avio_rb32(in
);
246 if (flags
& MOV_TRUN_FIRST_SAMPLE_FLAGS
) avio_rb32(in
);
249 for (i
= 0; i
< entries
&& pos
< end
; i
++) {
250 int sample_duration
= default_duration
;
251 if (flags
& MOV_TRUN_SAMPLE_DURATION
) sample_duration
= avio_rb32(in
);
252 if (flags
& MOV_TRUN_SAMPLE_SIZE
) avio_rb32(in
);
253 if (flags
& MOV_TRUN_SAMPLE_FLAGS
) avio_rb32(in
);
254 if (flags
& MOV_TRUN_SAMPLE_CTS
) avio_rb32(in
);
255 if (sample_duration
< 0) {
256 fprintf(stderr
, "Negative sample duration %d\n", sample_duration
);
259 ret
+= sample_duration
;
266 static int64_t read_moof_duration(AVIOContext
*in
, int64_t offset
)
269 int32_t moof_size
, size
, tag
;
271 int default_duration
= 0;
273 avio_seek(in
, offset
, SEEK_SET
);
274 moof_size
= avio_rb32(in
);
276 if (expect_tag(tag
, MKBETAG('m', 'o', 'o', 'f')) != 0)
278 while (pos
< offset
+ moof_size
) {
280 size
= avio_rb32(in
);
282 if (tag
== MKBETAG('t', 'r', 'a', 'f')) {
283 int64_t traf_pos
= pos
;
284 int64_t traf_size
= size
;
285 while (pos
< traf_pos
+ traf_size
) {
287 size
= avio_rb32(in
);
289 if (tag
== MKBETAG('t', 'f', 'h', 'd')) {
291 avio_r8(in
); /* version */
292 flags
= avio_rb24(in
);
293 avio_rb32(in
); /* track_id */
294 if (flags
& MOV_TFHD_BASE_DATA_OFFSET
)
296 if (flags
& MOV_TFHD_STSD_ID
)
298 if (flags
& MOV_TFHD_DEFAULT_DURATION
)
299 default_duration
= avio_rb32(in
);
301 if (tag
== MKBETAG('t', 'r', 'u', 'n')) {
302 return read_trun_duration(in
, default_duration
,
305 avio_seek(in
, pos
+ size
, SEEK_SET
);
307 fprintf(stderr
, "Couldn't find trun\n");
310 avio_seek(in
, pos
+ size
, SEEK_SET
);
312 fprintf(stderr
, "Couldn't find traf\n");
318 static int read_tfra(struct Tracks
*tracks
, int start_index
, AVIOContext
*f
)
320 int ret
= AVERROR_EOF
, track_id
;
321 int version
, fieldlength
, i
, j
;
322 int64_t pos
= avio_tell(f
);
323 uint32_t size
= avio_rb32(f
);
324 struct Track
*track
= NULL
;
326 if (avio_rb32(f
) != MKBETAG('t', 'f', 'r', 'a'))
328 version
= avio_r8(f
);
330 track_id
= avio_rb32(f
); /* track id */
331 for (i
= start_index
; i
< tracks
->nb_tracks
&& !track
; i
++)
332 if (tracks
->tracks
[i
]->track_id
== track_id
)
333 track
= tracks
->tracks
[i
];
335 /* Ok, continue parsing the next atom */
339 fieldlength
= avio_rb32(f
);
340 track
->chunks
= avio_rb32(f
);
341 track
->offsets
= av_mallocz_array(track
->chunks
, sizeof(*track
->offsets
));
342 if (!track
->offsets
) {
343 ret
= AVERROR(ENOMEM
);
346 // The duration here is always the difference between consecutive
348 for (i
= 0; i
< track
->chunks
; i
++) {
350 track
->offsets
[i
].time
= avio_rb64(f
);
351 track
->offsets
[i
].offset
= avio_rb64(f
);
353 track
->offsets
[i
].time
= avio_rb32(f
);
354 track
->offsets
[i
].offset
= avio_rb32(f
);
356 for (j
= 0; j
< ((fieldlength
>> 4) & 3) + 1; j
++)
358 for (j
= 0; j
< ((fieldlength
>> 2) & 3) + 1; j
++)
360 for (j
= 0; j
< ((fieldlength
>> 0) & 3) + 1; j
++)
363 track
->offsets
[i
- 1].duration
= track
->offsets
[i
].time
-
364 track
->offsets
[i
- 1].time
;
366 if (track
->chunks
> 0) {
367 track
->offsets
[track
->chunks
- 1].duration
= track
->offsets
[0].time
+
369 track
->offsets
[track
->chunks
- 1].time
;
371 // Now try and read the actual durations from the trun sample data.
372 for (i
= 0; i
< track
->chunks
; i
++) {
373 int64_t duration
= read_moof_duration(f
, track
->offsets
[i
].offset
);
374 if (duration
> 0 && abs(duration
- track
->offsets
[i
].duration
) > 3) {
375 // 3 allows for integer duration to drift a few units,
376 // e.g., for 1/3 durations
377 track
->offsets
[i
].duration
= duration
;
380 if (track
->chunks
> 0) {
381 if (track
->offsets
[track
->chunks
- 1].duration
<= 0) {
382 fprintf(stderr
, "Calculated last chunk duration for track %d "
383 "was non-positive (%"PRId64
"), probably due to missing "
384 "fragments ", track
->track_id
,
385 track
->offsets
[track
->chunks
- 1].duration
);
386 if (track
->chunks
> 1) {
387 track
->offsets
[track
->chunks
- 1].duration
=
388 track
->offsets
[track
->chunks
- 2].duration
;
390 track
->offsets
[track
->chunks
- 1].duration
= 1;
392 fprintf(stderr
, "corrected to %"PRId64
"\n",
393 track
->offsets
[track
->chunks
- 1].duration
);
394 track
->duration
= track
->offsets
[track
->chunks
- 1].time
+
395 track
->offsets
[track
->chunks
- 1].duration
-
396 track
->offsets
[0].time
;
397 fprintf(stderr
, "Track duration corrected to %"PRId64
"\n",
404 avio_seek(f
, pos
+ size
, SEEK_SET
);
408 static int read_mfra(struct Tracks
*tracks
, int start_index
,
409 const char *file
, int split
, int ismf
,
410 const char *basename
, const char* output_prefix
)
413 const char* err_str
= "";
414 AVIOContext
*f
= NULL
;
417 if ((err
= avio_open2(&f
, file
, AVIO_FLAG_READ
, NULL
, NULL
)) < 0)
419 avio_seek(f
, avio_size(f
) - 4, SEEK_SET
);
420 mfra_size
= avio_rb32(f
);
421 avio_seek(f
, -mfra_size
, SEEK_CUR
);
422 if (avio_rb32(f
) != mfra_size
) {
423 err
= AVERROR_INVALIDDATA
;
424 err_str
= "mfra size mismatch";
427 if (avio_rb32(f
) != MKBETAG('m', 'f', 'r', 'a')) {
428 err
= AVERROR_INVALIDDATA
;
429 err_str
= "mfra tag mismatch";
432 while (!read_tfra(tracks
, start_index
, f
)) {
437 err
= write_fragments(tracks
, start_index
, f
, basename
, split
, ismf
,
439 err_str
= "error in write_fragments";
445 fprintf(stderr
, "Unable to read the MFRA atom in %s (%s)\n", file
, err_str
);
449 static int get_private_data(struct Track
*track
, AVCodecContext
*codec
)
451 track
->codec_private_size
= codec
->extradata_size
;
452 track
->codec_private
= av_mallocz(codec
->extradata_size
);
453 if (!track
->codec_private
)
454 return AVERROR(ENOMEM
);
455 memcpy(track
->codec_private
, codec
->extradata
, codec
->extradata_size
);
459 static int get_video_private_data(struct Track
*track
, AVCodecContext
*codec
)
461 AVIOContext
*io
= NULL
;
462 uint16_t sps_size
, pps_size
;
465 if (codec
->codec_id
== AV_CODEC_ID_VC1
)
466 return get_private_data(track
, codec
);
468 if ((err
= avio_open_dyn_buf(&io
)) < 0)
470 err
= AVERROR(EINVAL
);
471 if (codec
->extradata_size
< 11 || codec
->extradata
[0] != 1)
473 sps_size
= AV_RB16(&codec
->extradata
[6]);
474 if (11 + sps_size
> codec
->extradata_size
)
476 avio_wb32(io
, 0x00000001);
477 avio_write(io
, &codec
->extradata
[8], sps_size
);
478 pps_size
= AV_RB16(&codec
->extradata
[9 + sps_size
]);
479 if (11 + sps_size
+ pps_size
> codec
->extradata_size
)
481 avio_wb32(io
, 0x00000001);
482 avio_write(io
, &codec
->extradata
[11 + sps_size
], pps_size
);
486 track
->codec_private_size
= avio_close_dyn_buf(io
, &track
->codec_private
);
490 static int handle_file(struct Tracks
*tracks
, const char *file
, int split
,
491 int ismf
, const char *basename
,
492 const char* output_prefix
)
494 AVFormatContext
*ctx
= NULL
;
495 int err
= 0, i
, orig_tracks
= tracks
->nb_tracks
;
496 char errbuf
[50], *ptr
;
499 err
= avformat_open_input(&ctx
, file
, NULL
, NULL
);
501 av_strerror(err
, errbuf
, sizeof(errbuf
));
502 fprintf(stderr
, "Unable to open %s: %s\n", file
, errbuf
);
506 err
= avformat_find_stream_info(ctx
, NULL
);
508 av_strerror(err
, errbuf
, sizeof(errbuf
));
509 fprintf(stderr
, "Unable to identify %s: %s\n", file
, errbuf
);
513 if (ctx
->nb_streams
< 1) {
514 fprintf(stderr
, "No streams found in %s\n", file
);
518 for (i
= 0; i
< ctx
->nb_streams
; i
++) {
520 AVStream
*st
= ctx
->streams
[i
];
522 if (st
->codec
->bit_rate
== 0) {
523 fprintf(stderr
, "Skipping track %d in %s as it has zero bitrate\n",
528 track
= av_mallocz(sizeof(*track
));
530 err
= AVERROR(ENOMEM
);
533 temp
= av_realloc(tracks
->tracks
,
534 sizeof(*tracks
->tracks
) * (tracks
->nb_tracks
+ 1));
537 err
= AVERROR(ENOMEM
);
540 tracks
->tracks
= temp
;
541 tracks
->tracks
[tracks
->nb_tracks
] = track
;
544 if ((ptr
= strrchr(file
, '/')))
545 track
->name
= ptr
+ 1;
547 track
->bitrate
= st
->codec
->bit_rate
;
548 track
->track_id
= st
->id
;
549 track
->timescale
= st
->time_base
.den
;
550 track
->duration
= st
->duration
;
551 track
->is_audio
= st
->codec
->codec_type
== AVMEDIA_TYPE_AUDIO
;
552 track
->is_video
= st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
;
554 if (!track
->is_audio
&& !track
->is_video
) {
556 "Track %d in %s is neither video nor audio, skipping\n",
557 track
->track_id
, file
);
558 av_freep(&tracks
->tracks
[tracks
->nb_tracks
]);
562 tracks
->duration
= FFMAX(tracks
->duration
,
563 av_rescale_rnd(track
->duration
, AV_TIME_BASE
,
564 track
->timescale
, AV_ROUND_UP
));
566 if (track
->is_audio
) {
567 if (tracks
->audio_track
< 0)
568 tracks
->audio_track
= tracks
->nb_tracks
;
569 tracks
->nb_audio_tracks
++;
570 track
->channels
= st
->codec
->channels
;
571 track
->sample_rate
= st
->codec
->sample_rate
;
572 if (st
->codec
->codec_id
== AV_CODEC_ID_AAC
) {
573 track
->fourcc
= "AACL";
575 track
->blocksize
= 4;
576 } else if (st
->codec
->codec_id
== AV_CODEC_ID_WMAPRO
) {
577 track
->fourcc
= "WMAP";
578 track
->tag
= st
->codec
->codec_tag
;
579 track
->blocksize
= st
->codec
->block_align
;
581 get_private_data(track
, st
->codec
);
583 if (track
->is_video
) {
584 if (tracks
->video_track
< 0)
585 tracks
->video_track
= tracks
->nb_tracks
;
586 tracks
->nb_video_tracks
++;
587 track
->width
= st
->codec
->width
;
588 track
->height
= st
->codec
->height
;
589 if (st
->codec
->codec_id
== AV_CODEC_ID_H264
)
590 track
->fourcc
= "H264";
591 else if (st
->codec
->codec_id
== AV_CODEC_ID_VC1
)
592 track
->fourcc
= "WVC1";
593 get_video_private_data(track
, st
->codec
);
599 avformat_close_input(&ctx
);
601 err
= read_mfra(tracks
, orig_tracks
, file
, split
, ismf
, basename
,
606 avformat_close_input(&ctx
);
610 static void output_server_manifest(struct Tracks
*tracks
, const char *basename
,
611 const char *output_prefix
,
612 const char *path_prefix
,
613 const char *ismc_prefix
)
619 snprintf(filename
, sizeof(filename
), "%s%s.ism", output_prefix
, basename
);
620 out
= fopen(filename
, "w");
625 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
626 fprintf(out
, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
627 fprintf(out
, "\t<head>\n");
628 fprintf(out
, "\t\t<meta name=\"clientManifestRelativePath\" "
629 "content=\"%s%s.ismc\" />\n", ismc_prefix
, basename
);
630 fprintf(out
, "\t</head>\n");
631 fprintf(out
, "\t<body>\n");
632 fprintf(out
, "\t\t<switch>\n");
633 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
634 struct Track
*track
= tracks
->tracks
[i
];
635 const char *type
= track
->is_video
? "video" : "audio";
636 fprintf(out
, "\t\t\t<%s src=\"%s%s\" systemBitrate=\"%d\">\n",
637 type
, path_prefix
, track
->name
, track
->bitrate
);
638 fprintf(out
, "\t\t\t\t<param name=\"trackID\" value=\"%d\" "
639 "valueType=\"data\" />\n", track
->track_id
);
640 fprintf(out
, "\t\t\t</%s>\n", type
);
642 fprintf(out
, "\t\t</switch>\n");
643 fprintf(out
, "\t</body>\n");
644 fprintf(out
, "</smil>\n");
648 static void print_track_chunks(FILE *out
, struct Tracks
*tracks
, int main
,
653 struct Track
*track
= tracks
->tracks
[main
];
654 int should_print_time_mismatch
= 1;
656 for (i
= 0; i
< track
->chunks
; i
++) {
657 for (j
= main
+ 1; j
< tracks
->nb_tracks
; j
++) {
658 if (tracks
->tracks
[j
]->is_audio
== track
->is_audio
) {
659 if (track
->offsets
[i
].duration
!= tracks
->tracks
[j
]->offsets
[i
].duration
) {
660 fprintf(stderr
, "Mismatched duration of %s chunk %d in %s (%d) and %s (%d)\n",
661 type
, i
, track
->name
, main
, tracks
->tracks
[j
]->name
, j
);
662 should_print_time_mismatch
= 1;
664 if (track
->offsets
[i
].time
!= tracks
->tracks
[j
]->offsets
[i
].time
) {
665 if (should_print_time_mismatch
)
666 fprintf(stderr
, "Mismatched (start) time of %s chunk %d in %s (%d) and %s (%d)\n",
667 type
, i
, track
->name
, main
, tracks
->tracks
[j
]->name
, j
);
668 should_print_time_mismatch
= 0;
672 fprintf(out
, "\t\t<c n=\"%d\" d=\"%"PRId64
"\" ",
673 i
, track
->offsets
[i
].duration
);
674 if (pos
!= track
->offsets
[i
].time
) {
675 fprintf(out
, "t=\"%"PRId64
"\" ", track
->offsets
[i
].time
);
676 pos
= track
->offsets
[i
].time
;
678 pos
+= track
->offsets
[i
].duration
;
679 fprintf(out
, "/>\n");
683 static void output_client_manifest(struct Tracks
*tracks
, const char *basename
,
684 const char *output_prefix
, int split
)
691 snprintf(filename
, sizeof(filename
), "%sManifest", output_prefix
);
693 snprintf(filename
, sizeof(filename
), "%s%s.ismc", output_prefix
, basename
);
694 out
= fopen(filename
, "w");
699 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
700 fprintf(out
, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" "
701 "Duration=\"%"PRId64
"\">\n", tracks
->duration
* 10);
702 if (tracks
->video_track
>= 0) {
703 struct Track
*track
= tracks
->tracks
[tracks
->video_track
];
704 struct Track
*first_track
= track
;
707 "\t<StreamIndex Type=\"video\" QualityLevels=\"%d\" "
709 "Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n",
710 tracks
->nb_video_tracks
, track
->chunks
);
711 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
712 track
= tracks
->tracks
[i
];
713 if (!track
->is_video
)
716 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
717 "FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" "
718 "CodecPrivateData=\"",
719 index
, track
->bitrate
, track
->fourcc
, track
->width
, track
->height
);
720 for (j
= 0; j
< track
->codec_private_size
; j
++)
721 fprintf(out
, "%02X", track
->codec_private
[j
]);
722 fprintf(out
, "\" />\n");
724 if (track
->chunks
!= first_track
->chunks
)
725 fprintf(stderr
, "Mismatched number of video chunks in %s (id: %d, chunks %d) and %s (id: %d, chunks %d)\n",
726 track
->name
, track
->track_id
, track
->chunks
, first_track
->name
, first_track
->track_id
, first_track
->chunks
);
728 print_track_chunks(out
, tracks
, tracks
->video_track
, "video");
729 fprintf(out
, "\t</StreamIndex>\n");
731 if (tracks
->audio_track
>= 0) {
732 struct Track
*track
= tracks
->tracks
[tracks
->audio_track
];
733 struct Track
*first_track
= track
;
736 "\t<StreamIndex Type=\"audio\" QualityLevels=\"%d\" "
738 "Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n",
739 tracks
->nb_audio_tracks
, track
->chunks
);
740 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
741 track
= tracks
->tracks
[i
];
742 if (!track
->is_audio
)
745 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
746 "FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" "
747 "BitsPerSample=\"16\" PacketSize=\"%d\" "
748 "AudioTag=\"%d\" CodecPrivateData=\"",
749 index
, track
->bitrate
, track
->fourcc
, track
->sample_rate
,
750 track
->channels
, track
->blocksize
, track
->tag
);
751 for (j
= 0; j
< track
->codec_private_size
; j
++)
752 fprintf(out
, "%02X", track
->codec_private
[j
]);
753 fprintf(out
, "\" />\n");
755 if (track
->chunks
!= first_track
->chunks
)
756 fprintf(stderr
, "Mismatched number of audio chunks in %s and %s\n",
757 track
->name
, first_track
->name
);
759 print_track_chunks(out
, tracks
, tracks
->audio_track
, "audio");
760 fprintf(out
, "\t</StreamIndex>\n");
762 fprintf(out
, "</SmoothStreamingMedia>\n");
766 static void clean_tracks(struct Tracks
*tracks
)
769 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
770 av_freep(&tracks
->tracks
[i
]->codec_private
);
771 av_freep(&tracks
->tracks
[i
]->offsets
);
772 av_freep(&tracks
->tracks
[i
]);
774 av_freep(&tracks
->tracks
);
775 tracks
->nb_tracks
= 0;
778 int main(int argc
, char **argv
)
780 const char *basename
= NULL
;
781 const char *path_prefix
= "", *ismc_prefix
= "";
782 const char *output_prefix
= "";
783 char output_prefix_buf
[2048];
784 int split
= 0, ismf
= 0, i
;
785 struct Tracks tracks
= { 0, .video_track
= -1, .audio_track
= -1 };
789 for (i
= 1; i
< argc
; i
++) {
790 if (!strcmp(argv
[i
], "-n")) {
791 basename
= argv
[i
+ 1];
793 } else if (!strcmp(argv
[i
], "-path-prefix")) {
794 path_prefix
= argv
[i
+ 1];
796 } else if (!strcmp(argv
[i
], "-ismc-prefix")) {
797 ismc_prefix
= argv
[i
+ 1];
799 } else if (!strcmp(argv
[i
], "-output")) {
800 output_prefix
= argv
[i
+ 1];
802 if (output_prefix
[strlen(output_prefix
) - 1] != '/') {
803 snprintf(output_prefix_buf
, sizeof(output_prefix_buf
),
804 "%s/", output_prefix
);
805 output_prefix
= output_prefix_buf
;
807 } else if (!strcmp(argv
[i
], "-split")) {
809 } else if (!strcmp(argv
[i
], "-ismf")) {
811 } else if (argv
[i
][0] == '-') {
812 return usage(argv
[0], 1);
816 if (handle_file(&tracks
, argv
[i
], split
, ismf
,
817 basename
, output_prefix
))
821 if (!tracks
.nb_tracks
|| (!basename
&& !split
))
822 return usage(argv
[0], 1);
825 output_server_manifest(&tracks
, basename
, output_prefix
,
826 path_prefix
, ismc_prefix
);
827 output_client_manifest(&tracks
, basename
, output_prefix
, split
);
829 clean_tracks(&tracks
);