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.)
50 #include "libavformat/avformat.h"
51 #include "libavformat/isom.h"
52 #include "libavformat/os_support.h"
53 #include "libavutil/intreadwrite.h"
54 #include "libavutil/mathematics.h"
56 static int usage(const char *argv0
, int ret
)
58 fprintf(stderr
, "%s [-split] [-ismf] [-n basename] [-path-prefix prefix] "
59 "[-ismc-prefix prefix] [-output dir] file1 [file2] ...\n", argv0
);
74 int is_audio
, is_video
;
77 int sample_rate
, channels
;
78 uint8_t *codec_private
;
79 int codec_private_size
;
80 struct MoofOffset
*offsets
;
90 struct Track
**tracks
;
91 int video_track
, audio_track
;
92 int nb_video_tracks
, nb_audio_tracks
;
95 static int expect_tag(int32_t got_tag
, int32_t expected_tag
) {
96 if (got_tag
!= expected_tag
) {
97 char got_tag_str
[4], expected_tag_str
[4];
98 AV_WB32(got_tag_str
, got_tag
);
99 AV_WB32(expected_tag_str
, expected_tag
);
100 fprintf(stderr
, "wanted tag %.4s, got %.4s\n", expected_tag_str
,
107 static int copy_tag(AVIOContext
*in
, AVIOContext
*out
, int32_t tag_name
)
111 size
= avio_rb32(in
);
113 avio_wb32(out
, size
);
115 if (expect_tag(tag
, tag_name
) != 0)
120 int len
= FFMIN(sizeof(buf
), size
);
122 if ((got
= avio_read(in
, buf
, len
)) != len
) {
123 fprintf(stderr
, "short read, wanted %d, got %d\n", len
, got
);
126 avio_write(out
, buf
, len
);
132 static int skip_tag(AVIOContext
*in
, int32_t tag_name
)
134 int64_t pos
= avio_tell(in
);
137 size
= avio_rb32(in
);
139 if (expect_tag(tag
, tag_name
) != 0)
141 avio_seek(in
, pos
+ size
, SEEK_SET
);
145 static int write_fragment(const char *filename
, AVIOContext
*in
)
147 AVIOContext
*out
= NULL
;
150 if ((ret
= avio_open2(&out
, filename
, AVIO_FLAG_WRITE
, NULL
, NULL
)) < 0) {
152 av_strerror(ret
, errbuf
, sizeof(errbuf
));
153 fprintf(stderr
, "Unable to open %s: %s\n", filename
, errbuf
);
156 ret
= copy_tag(in
, out
, MKBETAG('m', 'o', 'o', 'f'));
158 ret
= copy_tag(in
, out
, MKBETAG('m', 'd', 'a', 't'));
166 static int skip_fragment(AVIOContext
*in
)
169 ret
= skip_tag(in
, MKBETAG('m', 'o', 'o', 'f'));
171 ret
= skip_tag(in
, MKBETAG('m', 'd', 'a', 't'));
175 static int write_fragments(struct Tracks
*tracks
, int start_index
,
176 AVIOContext
*in
, const char *basename
,
177 int split
, int ismf
, const char* output_prefix
)
179 char dirname
[2048], filename
[2048], idxname
[2048];
180 int i
, j
, ret
= 0, fragment_ret
;
184 snprintf(idxname
, sizeof(idxname
), "%s%s.ismf", output_prefix
, basename
);
185 out
= fopen(idxname
, "w");
187 ret
= AVERROR(errno
);
192 for (i
= start_index
; i
< tracks
->nb_tracks
; i
++) {
193 struct Track
*track
= tracks
->tracks
[i
];
194 const char *type
= track
->is_video
? "video" : "audio";
195 snprintf(dirname
, sizeof(dirname
), "%sQualityLevels(%d)", output_prefix
, track
->bitrate
);
197 if (mkdir(dirname
, 0777) == -1 && errno
!= EEXIST
) {
198 ret
= AVERROR(errno
);
203 for (j
= 0; j
< track
->chunks
; j
++) {
204 snprintf(filename
, sizeof(filename
), "%s/Fragments(%s=%"PRId64
")",
205 dirname
, type
, track
->offsets
[j
].time
);
206 avio_seek(in
, track
->offsets
[j
].offset
, SEEK_SET
);
208 fprintf(out
, "%s %"PRId64
, filename
, avio_tell(in
));
210 fragment_ret
= write_fragment(filename
, in
);
212 fragment_ret
= skip_fragment(in
);
214 fprintf(out
, " %"PRId64
"\n", avio_tell(in
));
215 if (fragment_ret
!= 0) {
216 fprintf(stderr
, "failed fragment %d in track %d (%s)\n", j
,
217 track
->track_id
, track
->name
);
228 static int64_t read_trun_duration(AVIOContext
*in
, int default_duration
,
235 int64_t first_pts
= 0;
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
;
252 if (flags
& MOV_TRUN_SAMPLE_DURATION
) sample_duration
= avio_rb32(in
);
253 if (flags
& MOV_TRUN_SAMPLE_SIZE
) avio_rb32(in
);
254 if (flags
& MOV_TRUN_SAMPLE_FLAGS
) avio_rb32(in
);
255 if (flags
& MOV_TRUN_SAMPLE_CTS
) pts
+= avio_rb32(in
);
256 if (sample_duration
< 0) {
257 fprintf(stderr
, "Negative sample duration %d\n", sample_duration
);
262 max_pts
= FFMAX(max_pts
, pts
+ sample_duration
);
263 dts
+= sample_duration
;
267 return max_pts
- first_pts
;
270 static int64_t read_moof_duration(AVIOContext
*in
, int64_t offset
)
273 int32_t moof_size
, size
, tag
;
275 int default_duration
= 0;
277 avio_seek(in
, offset
, SEEK_SET
);
278 moof_size
= avio_rb32(in
);
280 if (expect_tag(tag
, MKBETAG('m', 'o', 'o', 'f')) != 0)
282 while (pos
< offset
+ moof_size
) {
284 size
= avio_rb32(in
);
286 if (tag
== MKBETAG('t', 'r', 'a', 'f')) {
287 int64_t traf_pos
= pos
;
288 int64_t traf_size
= size
;
289 while (pos
< traf_pos
+ traf_size
) {
291 size
= avio_rb32(in
);
293 if (tag
== MKBETAG('t', 'f', 'h', 'd')) {
295 avio_r8(in
); /* version */
296 flags
= avio_rb24(in
);
297 avio_rb32(in
); /* track_id */
298 if (flags
& MOV_TFHD_BASE_DATA_OFFSET
)
300 if (flags
& MOV_TFHD_STSD_ID
)
302 if (flags
& MOV_TFHD_DEFAULT_DURATION
)
303 default_duration
= avio_rb32(in
);
305 if (tag
== MKBETAG('t', 'r', 'u', 'n')) {
306 return read_trun_duration(in
, default_duration
,
309 avio_seek(in
, pos
+ size
, SEEK_SET
);
311 fprintf(stderr
, "Couldn't find trun\n");
314 avio_seek(in
, pos
+ size
, SEEK_SET
);
316 fprintf(stderr
, "Couldn't find traf\n");
322 static int read_tfra(struct Tracks
*tracks
, int start_index
, AVIOContext
*f
)
324 int ret
= AVERROR_EOF
, track_id
;
325 int version
, fieldlength
, i
, j
;
326 int64_t pos
= avio_tell(f
);
327 uint32_t size
= avio_rb32(f
);
328 struct Track
*track
= NULL
;
330 if (avio_rb32(f
) != MKBETAG('t', 'f', 'r', 'a'))
332 version
= avio_r8(f
);
334 track_id
= avio_rb32(f
); /* track id */
335 for (i
= start_index
; i
< tracks
->nb_tracks
&& !track
; i
++)
336 if (tracks
->tracks
[i
]->track_id
== track_id
)
337 track
= tracks
->tracks
[i
];
339 /* Ok, continue parsing the next atom */
343 fieldlength
= avio_rb32(f
);
344 track
->chunks
= avio_rb32(f
);
345 track
->offsets
= av_calloc(track
->chunks
, sizeof(*track
->offsets
));
346 if (!track
->offsets
) {
348 ret
= AVERROR(ENOMEM
);
351 // The duration here is always the difference between consecutive
353 for (i
= 0; i
< track
->chunks
; i
++) {
355 track
->offsets
[i
].time
= avio_rb64(f
);
356 track
->offsets
[i
].offset
= avio_rb64(f
);
358 track
->offsets
[i
].time
= avio_rb32(f
);
359 track
->offsets
[i
].offset
= avio_rb32(f
);
361 for (j
= 0; j
< ((fieldlength
>> 4) & 3) + 1; j
++)
363 for (j
= 0; j
< ((fieldlength
>> 2) & 3) + 1; j
++)
365 for (j
= 0; j
< ((fieldlength
>> 0) & 3) + 1; j
++)
368 track
->offsets
[i
- 1].duration
= track
->offsets
[i
].time
-
369 track
->offsets
[i
- 1].time
;
371 if (track
->chunks
> 0) {
372 track
->offsets
[track
->chunks
- 1].duration
= track
->offsets
[0].time
+
374 track
->offsets
[track
->chunks
- 1].time
;
376 // Now try to read the actual durations from the trun sample data.
377 for (i
= 0; i
< track
->chunks
; i
++) {
378 int64_t duration
= read_moof_duration(f
, track
->offsets
[i
].offset
);
379 if (duration
> 0 && llabs(duration
- track
->offsets
[i
].duration
) > 3) {
380 // 3 allows for integer duration to drift a few units,
381 // e.g., for 1/3 durations
382 track
->offsets
[i
].duration
= duration
;
385 if (track
->chunks
> 0) {
386 if (track
->offsets
[track
->chunks
- 1].duration
<= 0) {
387 fprintf(stderr
, "Calculated last chunk duration for track %d "
388 "was non-positive (%"PRId64
"), probably due to missing "
389 "fragments ", track
->track_id
,
390 track
->offsets
[track
->chunks
- 1].duration
);
391 if (track
->chunks
> 1) {
392 track
->offsets
[track
->chunks
- 1].duration
=
393 track
->offsets
[track
->chunks
- 2].duration
;
395 track
->offsets
[track
->chunks
- 1].duration
= 1;
397 fprintf(stderr
, "corrected to %"PRId64
"\n",
398 track
->offsets
[track
->chunks
- 1].duration
);
399 track
->duration
= track
->offsets
[track
->chunks
- 1].time
+
400 track
->offsets
[track
->chunks
- 1].duration
-
401 track
->offsets
[0].time
;
402 fprintf(stderr
, "Track duration corrected to %"PRId64
"\n",
409 avio_seek(f
, pos
+ size
, SEEK_SET
);
413 static int read_mfra(struct Tracks
*tracks
, int start_index
,
414 const char *file
, int split
, int ismf
,
415 const char *basename
, const char* output_prefix
)
418 const char* err_str
= "";
419 AVIOContext
*f
= NULL
;
422 if ((err
= avio_open2(&f
, file
, AVIO_FLAG_READ
, NULL
, NULL
)) < 0)
424 avio_seek(f
, avio_size(f
) - 4, SEEK_SET
);
425 mfra_size
= avio_rb32(f
);
426 avio_seek(f
, -mfra_size
, SEEK_CUR
);
427 if (avio_rb32(f
) != mfra_size
) {
428 err
= AVERROR_INVALIDDATA
;
429 err_str
= "mfra size mismatch";
432 if (avio_rb32(f
) != MKBETAG('m', 'f', 'r', 'a')) {
433 err
= AVERROR_INVALIDDATA
;
434 err_str
= "mfra tag mismatch";
437 while (!read_tfra(tracks
, start_index
, f
)) {
442 err
= write_fragments(tracks
, start_index
, f
, basename
, split
, ismf
,
444 err_str
= "error in write_fragments";
450 fprintf(stderr
, "Unable to read the MFRA atom in %s (%s)\n", file
, err_str
);
454 static int get_private_data(struct Track
*track
, AVCodecParameters
*codecpar
)
456 track
->codec_private_size
= 0;
457 track
->codec_private
= av_mallocz(codecpar
->extradata_size
);
458 if (!track
->codec_private
)
459 return AVERROR(ENOMEM
);
460 track
->codec_private_size
= codecpar
->extradata_size
;
461 memcpy(track
->codec_private
, codecpar
->extradata
, codecpar
->extradata_size
);
465 static int get_video_private_data(struct Track
*track
, AVCodecParameters
*codecpar
)
467 AVIOContext
*io
= NULL
;
468 uint16_t sps_size
, pps_size
;
471 if (codecpar
->codec_id
== AV_CODEC_ID_VC1
)
472 return get_private_data(track
, codecpar
);
474 if ((err
= avio_open_dyn_buf(&io
)) < 0)
476 err
= AVERROR(EINVAL
);
477 if (codecpar
->extradata_size
< 11 || codecpar
->extradata
[0] != 1)
479 sps_size
= AV_RB16(&codecpar
->extradata
[6]);
480 if (11 + sps_size
> codecpar
->extradata_size
)
482 avio_wb32(io
, 0x00000001);
483 avio_write(io
, &codecpar
->extradata
[8], sps_size
);
484 pps_size
= AV_RB16(&codecpar
->extradata
[9 + sps_size
]);
485 if (11 + sps_size
+ pps_size
> codecpar
->extradata_size
)
487 avio_wb32(io
, 0x00000001);
488 avio_write(io
, &codecpar
->extradata
[11 + sps_size
], pps_size
);
492 track
->codec_private_size
= avio_close_dyn_buf(io
, &track
->codec_private
);
496 static int handle_file(struct Tracks
*tracks
, const char *file
, int split
,
497 int ismf
, const char *basename
,
498 const char* output_prefix
)
500 AVFormatContext
*ctx
= NULL
;
501 int err
= 0, i
, orig_tracks
= tracks
->nb_tracks
;
502 char errbuf
[50], *ptr
;
505 err
= avformat_open_input(&ctx
, file
, NULL
, NULL
);
507 av_strerror(err
, errbuf
, sizeof(errbuf
));
508 fprintf(stderr
, "Unable to open %s: %s\n", file
, errbuf
);
512 err
= avformat_find_stream_info(ctx
, NULL
);
514 av_strerror(err
, errbuf
, sizeof(errbuf
));
515 fprintf(stderr
, "Unable to identify %s: %s\n", file
, errbuf
);
519 if (ctx
->nb_streams
< 1) {
520 fprintf(stderr
, "No streams found in %s\n", file
);
524 for (i
= 0; i
< ctx
->nb_streams
; i
++) {
526 AVStream
*st
= ctx
->streams
[i
];
528 if (st
->codecpar
->bit_rate
== 0) {
529 fprintf(stderr
, "Skipping track %d in %s as it has zero bitrate\n",
534 track
= av_mallocz(sizeof(*track
));
536 err
= AVERROR(ENOMEM
);
539 temp
= av_realloc_array(tracks
->tracks
,
540 tracks
->nb_tracks
+ 1,
541 sizeof(*tracks
->tracks
));
544 err
= AVERROR(ENOMEM
);
547 tracks
->tracks
= temp
;
548 tracks
->tracks
[tracks
->nb_tracks
] = track
;
551 if ((ptr
= strrchr(file
, '/')))
552 track
->name
= ptr
+ 1;
554 track
->bitrate
= st
->codecpar
->bit_rate
;
555 track
->track_id
= st
->id
;
556 track
->timescale
= st
->time_base
.den
;
557 track
->duration
= st
->duration
;
558 track
->is_audio
= st
->codecpar
->codec_type
== AVMEDIA_TYPE_AUDIO
;
559 track
->is_video
= st
->codecpar
->codec_type
== AVMEDIA_TYPE_VIDEO
;
561 if (!track
->is_audio
&& !track
->is_video
) {
563 "Track %d in %s is neither video nor audio, skipping\n",
564 track
->track_id
, file
);
565 av_freep(&tracks
->tracks
[tracks
->nb_tracks
]);
569 tracks
->duration
= FFMAX(tracks
->duration
,
570 av_rescale_rnd(track
->duration
, AV_TIME_BASE
,
571 track
->timescale
, AV_ROUND_UP
));
573 if (track
->is_audio
) {
574 if (tracks
->audio_track
< 0)
575 tracks
->audio_track
= tracks
->nb_tracks
;
576 tracks
->nb_audio_tracks
++;
577 track
->channels
= st
->codecpar
->ch_layout
.nb_channels
;
578 track
->sample_rate
= st
->codecpar
->sample_rate
;
579 if (st
->codecpar
->codec_id
== AV_CODEC_ID_AAC
) {
580 track
->fourcc
= "AACL";
582 track
->blocksize
= 4;
583 } else if (st
->codecpar
->codec_id
== AV_CODEC_ID_WMAPRO
) {
584 track
->fourcc
= "WMAP";
585 track
->tag
= st
->codecpar
->codec_tag
;
586 track
->blocksize
= st
->codecpar
->block_align
;
588 get_private_data(track
, st
->codecpar
);
590 if (track
->is_video
) {
591 if (tracks
->video_track
< 0)
592 tracks
->video_track
= tracks
->nb_tracks
;
593 tracks
->nb_video_tracks
++;
594 track
->width
= st
->codecpar
->width
;
595 track
->height
= st
->codecpar
->height
;
596 if (st
->codecpar
->codec_id
== AV_CODEC_ID_H264
)
597 track
->fourcc
= "H264";
598 else if (st
->codecpar
->codec_id
== AV_CODEC_ID_VC1
)
599 track
->fourcc
= "WVC1";
600 get_video_private_data(track
, st
->codecpar
);
606 avformat_close_input(&ctx
);
608 err
= read_mfra(tracks
, orig_tracks
, file
, split
, ismf
, basename
,
613 avformat_close_input(&ctx
);
617 static void output_server_manifest(struct Tracks
*tracks
, const char *basename
,
618 const char *output_prefix
,
619 const char *path_prefix
,
620 const char *ismc_prefix
)
626 snprintf(filename
, sizeof(filename
), "%s%s.ism", output_prefix
, basename
);
627 out
= fopen(filename
, "w");
632 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
633 fprintf(out
, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
634 fprintf(out
, "\t<head>\n");
635 fprintf(out
, "\t\t<meta name=\"clientManifestRelativePath\" "
636 "content=\"%s%s.ismc\" />\n", ismc_prefix
, basename
);
637 fprintf(out
, "\t</head>\n");
638 fprintf(out
, "\t<body>\n");
639 fprintf(out
, "\t\t<switch>\n");
640 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
641 struct Track
*track
= tracks
->tracks
[i
];
642 const char *type
= track
->is_video
? "video" : "audio";
643 fprintf(out
, "\t\t\t<%s src=\"%s%s\" systemBitrate=\"%d\">\n",
644 type
, path_prefix
, track
->name
, track
->bitrate
);
645 fprintf(out
, "\t\t\t\t<param name=\"trackID\" value=\"%d\" "
646 "valueType=\"data\" />\n", track
->track_id
);
647 fprintf(out
, "\t\t\t</%s>\n", type
);
649 fprintf(out
, "\t\t</switch>\n");
650 fprintf(out
, "\t</body>\n");
651 fprintf(out
, "</smil>\n");
655 static void print_track_chunks(FILE *out
, struct Tracks
*tracks
, int main
,
660 struct Track
*track
= tracks
->tracks
[main
];
661 int should_print_time_mismatch
= 1;
663 for (i
= 0; i
< track
->chunks
; i
++) {
664 for (j
= main
+ 1; j
< tracks
->nb_tracks
; j
++) {
665 if (tracks
->tracks
[j
]->is_audio
== track
->is_audio
) {
666 if (track
->offsets
[i
].duration
!= tracks
->tracks
[j
]->offsets
[i
].duration
) {
667 fprintf(stderr
, "Mismatched duration of %s chunk %d in %s (%d) and %s (%d)\n",
668 type
, i
, track
->name
, main
, tracks
->tracks
[j
]->name
, j
);
669 should_print_time_mismatch
= 1;
671 if (track
->offsets
[i
].time
!= tracks
->tracks
[j
]->offsets
[i
].time
) {
672 if (should_print_time_mismatch
)
673 fprintf(stderr
, "Mismatched (start) time of %s chunk %d in %s (%d) and %s (%d)\n",
674 type
, i
, track
->name
, main
, tracks
->tracks
[j
]->name
, j
);
675 should_print_time_mismatch
= 0;
679 fprintf(out
, "\t\t<c n=\"%d\" d=\"%"PRId64
"\" ",
680 i
, track
->offsets
[i
].duration
);
681 if (pos
!= track
->offsets
[i
].time
) {
682 fprintf(out
, "t=\"%"PRId64
"\" ", track
->offsets
[i
].time
);
683 pos
= track
->offsets
[i
].time
;
685 pos
+= track
->offsets
[i
].duration
;
686 fprintf(out
, "/>\n");
690 static void output_client_manifest(struct Tracks
*tracks
, const char *basename
,
691 const char *output_prefix
, int split
)
698 snprintf(filename
, sizeof(filename
), "%sManifest", output_prefix
);
700 snprintf(filename
, sizeof(filename
), "%s%s.ismc", output_prefix
, basename
);
701 out
= fopen(filename
, "w");
706 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
707 fprintf(out
, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" "
708 "Duration=\"%"PRId64
"\">\n", tracks
->duration
* 10);
709 if (tracks
->video_track
>= 0) {
710 struct Track
*track
= tracks
->tracks
[tracks
->video_track
];
711 struct Track
*first_track
= track
;
714 "\t<StreamIndex Type=\"video\" QualityLevels=\"%d\" "
716 "Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n",
717 tracks
->nb_video_tracks
, track
->chunks
);
718 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
719 track
= tracks
->tracks
[i
];
720 if (!track
->is_video
)
723 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
724 "FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" "
725 "CodecPrivateData=\"",
726 index
, track
->bitrate
, track
->fourcc
, track
->width
, track
->height
);
727 for (j
= 0; j
< track
->codec_private_size
; j
++)
728 fprintf(out
, "%02X", track
->codec_private
[j
]);
729 fprintf(out
, "\" />\n");
731 if (track
->chunks
!= first_track
->chunks
)
732 fprintf(stderr
, "Mismatched number of video chunks in %s (id: %d, chunks %d) and %s (id: %d, chunks %d)\n",
733 track
->name
, track
->track_id
, track
->chunks
, first_track
->name
, first_track
->track_id
, first_track
->chunks
);
735 print_track_chunks(out
, tracks
, tracks
->video_track
, "video");
736 fprintf(out
, "\t</StreamIndex>\n");
738 if (tracks
->audio_track
>= 0) {
739 struct Track
*track
= tracks
->tracks
[tracks
->audio_track
];
740 struct Track
*first_track
= track
;
743 "\t<StreamIndex Type=\"audio\" QualityLevels=\"%d\" "
745 "Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n",
746 tracks
->nb_audio_tracks
, track
->chunks
);
747 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
748 track
= tracks
->tracks
[i
];
749 if (!track
->is_audio
)
752 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
753 "FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" "
754 "BitsPerSample=\"16\" PacketSize=\"%d\" "
755 "AudioTag=\"%d\" CodecPrivateData=\"",
756 index
, track
->bitrate
, track
->fourcc
, track
->sample_rate
,
757 track
->channels
, track
->blocksize
, track
->tag
);
758 for (j
= 0; j
< track
->codec_private_size
; j
++)
759 fprintf(out
, "%02X", track
->codec_private
[j
]);
760 fprintf(out
, "\" />\n");
762 if (track
->chunks
!= first_track
->chunks
)
763 fprintf(stderr
, "Mismatched number of audio chunks in %s and %s\n",
764 track
->name
, first_track
->name
);
766 print_track_chunks(out
, tracks
, tracks
->audio_track
, "audio");
767 fprintf(out
, "\t</StreamIndex>\n");
769 fprintf(out
, "</SmoothStreamingMedia>\n");
773 static void clean_tracks(struct Tracks
*tracks
)
776 for (i
= 0; i
< tracks
->nb_tracks
; i
++) {
777 av_freep(&tracks
->tracks
[i
]->codec_private
);
778 av_freep(&tracks
->tracks
[i
]->offsets
);
779 av_freep(&tracks
->tracks
[i
]);
781 av_freep(&tracks
->tracks
);
782 tracks
->nb_tracks
= 0;
785 int main(int argc
, char **argv
)
787 const char *basename
= NULL
;
788 const char *path_prefix
= "", *ismc_prefix
= "";
789 const char *output_prefix
= "";
790 char output_prefix_buf
[2048];
791 int split
= 0, ismf
= 0, i
;
792 struct Tracks tracks
= { 0, .video_track
= -1, .audio_track
= -1 };
794 for (i
= 1; i
< argc
; i
++) {
795 if (!strcmp(argv
[i
], "-n")) {
796 basename
= argv
[i
+ 1];
798 } else if (!strcmp(argv
[i
], "-path-prefix")) {
799 path_prefix
= argv
[i
+ 1];
801 } else if (!strcmp(argv
[i
], "-ismc-prefix")) {
802 ismc_prefix
= argv
[i
+ 1];
804 } else if (!strcmp(argv
[i
], "-output")) {
805 output_prefix
= argv
[i
+ 1];
807 if (output_prefix
[strlen(output_prefix
) - 1] != '/') {
808 snprintf(output_prefix_buf
, sizeof(output_prefix_buf
),
809 "%s/", output_prefix
);
810 output_prefix
= output_prefix_buf
;
812 } else if (!strcmp(argv
[i
], "-split")) {
814 } else if (!strcmp(argv
[i
], "-ismf")) {
816 } else if (argv
[i
][0] == '-') {
817 return usage(argv
[0], 1);
821 if (handle_file(&tracks
, argv
[i
], split
, ismf
,
822 basename
, output_prefix
))
826 if (!tracks
.nb_tracks
|| (!basename
&& !split
))
827 return usage(argv
[0], 1);
830 output_server_manifest(&tracks
, basename
, output_prefix
,
831 path_prefix
, ismc_prefix
);
832 output_client_manifest(&tracks
, basename
, output_prefix
, split
);
834 clean_tracks(&tracks
);