2 * Copyright (c) 2012 Martin Storsjo
4 * This file is part of Libav.
6 * Libav 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 * Libav 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 Libav; 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 * avconv <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 * To pre-split files for serving as static files by a web server without
29 * any extra server support, create the ismv file as above, and split it:
30 * ismindex -split foo.ismv
31 * This step creates a file Manifest and directories QualityLevel(...),
32 * that can be read directly by a smooth streaming player.
40 #define mkdir(a, b) _mkdir(a)
43 #include "libavformat/avformat.h"
44 #include "libavutil/intreadwrite.h"
45 #include "libavutil/mathematics.h"
47 static int usage(const char *argv0
, int ret
)
49 fprintf(stderr
, "%s [-split] [-n basename] file1 [file2] ...\n", argv0
);
64 int is_audio
, is_video
;
67 int sample_rate
, channels
;
68 uint8_t *codec_private
;
69 int codec_private_size
;
70 struct MoofOffset
*offsets
;
80 struct VideoFile
**files
;
81 int video_file
, audio_file
;
82 int nb_video_files
, nb_audio_files
;
85 static int copy_tag(AVIOContext
*in
, AVIOContext
*out
, int32_t tag_name
)
98 int len
= FFMIN(sizeof(buf
), size
);
99 if (avio_read(in
, buf
, len
) != len
)
101 avio_write(out
, buf
, len
);
107 static int write_fragment(const char *filename
, AVIOContext
*in
)
109 AVIOContext
*out
= NULL
;
112 if ((ret
= avio_open2(&out
, filename
, AVIO_FLAG_WRITE
, NULL
, NULL
)) < 0)
114 copy_tag(in
, out
, MKBETAG('m', 'o', 'o', 'f'));
115 copy_tag(in
, out
, MKBETAG('m', 'd', 'a', 't'));
123 static int write_fragments(struct VideoFiles
*files
, int start_index
,
126 char dirname
[100], filename
[500];
129 for (i
= start_index
; i
< files
->nb_files
; i
++) {
130 struct VideoFile
*vf
= files
->files
[i
];
131 const char *type
= vf
->is_video ?
"video" : "audio";
132 snprintf(dirname
, sizeof(dirname
), "QualityLevels(%d)", vf
->bitrate
);
133 mkdir(dirname
, 0777);
134 for (j
= 0; j
< vf
->chunks
; j
++) {
135 snprintf(filename
, sizeof(filename
), "%s/Fragments(%s=%"PRId64
")",
136 dirname
, type
, vf
->offsets
[j
].time
);
137 avio_seek(in
, vf
->offsets
[j
].offset
, SEEK_SET
);
138 write_fragment(filename
, in
);
144 static int read_tfra(struct VideoFiles
*files
, int start_index
, AVIOContext
*f
)
146 int ret
= AVERROR_EOF
, track_id
;
147 int version
, fieldlength
, i
, j
;
148 int64_t pos
= avio_tell(f
);
149 uint32_t size
= avio_rb32(f
);
150 struct VideoFile
*vf
= NULL
;
152 if (avio_rb32(f
) != MKBETAG('t', 'f', 'r', 'a'))
154 version
= avio_r8(f
);
156 track_id
= avio_rb32(f
); /* track id */
157 for (i
= start_index
; i
< files
->nb_files
&& !vf
; i
++)
158 if (files
->files
[i
]->track_id
== track_id
)
159 vf
= files
->files
[i
];
161 /* Ok, continue parsing the next atom */
165 fieldlength
= avio_rb32(f
);
166 vf
->chunks
= avio_rb32(f
);
167 vf
->offsets
= av_mallocz(sizeof(*vf
->offsets
) * vf
->chunks
);
169 ret
= AVERROR(ENOMEM
);
172 for (i
= 0; i
< vf
->chunks
; i
++) {
174 vf
->offsets
[i
].time
= avio_rb64(f
);
175 vf
->offsets
[i
].offset
= avio_rb64(f
);
177 vf
->offsets
[i
].time
= avio_rb32(f
);
178 vf
->offsets
[i
].offset
= avio_rb32(f
);
180 for (j
= 0; j
< ((fieldlength
>> 4) & 3) + 1; j
++)
182 for (j
= 0; j
< ((fieldlength
>> 2) & 3) + 1; j
++)
184 for (j
= 0; j
< ((fieldlength
>> 0) & 3) + 1; j
++)
187 vf
->offsets
[i
- 1].duration
= vf
->offsets
[i
].time
-
188 vf
->offsets
[i
- 1].time
;
191 vf
->offsets
[vf
->chunks
- 1].duration
= vf
->duration
-
192 vf
->offsets
[vf
->chunks
- 1].time
;
196 avio_seek(f
, pos
+ size
, SEEK_SET
);
200 static int read_mfra(struct VideoFiles
*files
, int start_index
,
201 const char *file
, int split
)
204 AVIOContext
*f
= NULL
;
207 if ((err
= avio_open2(&f
, file
, AVIO_FLAG_READ
, NULL
, NULL
)) < 0)
209 avio_seek(f
, avio_size(f
) - 4, SEEK_SET
);
210 mfra_size
= avio_rb32(f
);
211 avio_seek(f
, -mfra_size
, SEEK_CUR
);
212 if (avio_rb32(f
) != mfra_size
) {
213 err
= AVERROR_INVALIDDATA
;
216 if (avio_rb32(f
) != MKBETAG('m', 'f', 'r', 'a')) {
217 err
= AVERROR_INVALIDDATA
;
220 while (!read_tfra(files
, start_index
, f
)) {
225 write_fragments(files
, start_index
, f
);
231 fprintf(stderr
, "Unable to read the MFRA atom in %s\n", file
);
235 static int get_private_data(struct VideoFile
*vf
, AVCodecContext
*codec
)
237 vf
->codec_private_size
= codec
->extradata_size
;
238 vf
->codec_private
= av_mallocz(codec
->extradata_size
);
239 if (!vf
->codec_private
)
240 return AVERROR(ENOMEM
);
241 memcpy(vf
->codec_private
, codec
->extradata
, codec
->extradata_size
);
245 static int get_video_private_data(struct VideoFile
*vf
, AVCodecContext
*codec
)
247 AVIOContext
*io
= NULL
;
248 uint16_t sps_size
, pps_size
;
249 int err
= AVERROR(EINVAL
);
251 if (codec
->codec_id
== AV_CODEC_ID_VC1
)
252 return get_private_data(vf
, codec
);
254 avio_open_dyn_buf(&io
);
255 if (codec
->extradata_size
< 11 || codec
->extradata
[0] != 1)
257 sps_size
= AV_RB16(&codec
->extradata
[6]);
258 if (11 + sps_size
> codec
->extradata_size
)
260 avio_wb32(io
, 0x00000001);
261 avio_write(io
, &codec
->extradata
[8], sps_size
);
262 pps_size
= AV_RB16(&codec
->extradata
[9 + sps_size
]);
263 if (11 + sps_size
+ pps_size
> codec
->extradata_size
)
265 avio_wb32(io
, 0x00000001);
266 avio_write(io
, &codec
->extradata
[11 + sps_size
], pps_size
);
270 vf
->codec_private_size
= avio_close_dyn_buf(io
, &vf
->codec_private
);
274 static int handle_file(struct VideoFiles
*files
, const char *file
, int split
)
276 AVFormatContext
*ctx
= NULL
;
277 int err
= 0, i
, orig_files
= files
->nb_files
;
278 char errbuf
[50], *ptr
;
279 struct VideoFile
*vf
;
281 err
= avformat_open_input(&ctx
, file
, NULL
, NULL
);
283 av_strerror(err
, errbuf
, sizeof(errbuf
));
284 fprintf(stderr
, "Unable to open %s: %s\n", file
, errbuf
);
288 err
= avformat_find_stream_info(ctx
, NULL
);
290 av_strerror(err
, errbuf
, sizeof(errbuf
));
291 fprintf(stderr
, "Unable to identify %s: %s\n", file
, errbuf
);
295 if (ctx
->nb_streams
< 1) {
296 fprintf(stderr
, "No streams found in %s\n", file
);
299 if (!files
->duration
)
300 files
->duration
= ctx
->duration
;
302 for (i
= 0; i
< ctx
->nb_streams
; i
++) {
303 AVStream
*st
= ctx
->streams
[i
];
304 vf
= av_mallocz(sizeof(*vf
));
305 files
->files
= av_realloc(files
->files
,
306 sizeof(*files
->files
) * (files
->nb_files
+ 1));
307 files
->files
[files
->nb_files
] = vf
;
310 if ((ptr
= strrchr(file
, '/')) != NULL
)
313 vf
->bitrate
= st
->codec
->bit_rate
;
314 vf
->track_id
= st
->id
;
315 vf
->timescale
= st
->time_base
.den
;
316 vf
->duration
= av_rescale_rnd(ctx
->duration
, vf
->timescale
,
317 AV_TIME_BASE
, AV_ROUND_UP
);
318 vf
->is_audio
= st
->codec
->codec_type
== AVMEDIA_TYPE_AUDIO
;
319 vf
->is_video
= st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
;
321 if (!vf
->is_audio
&& !vf
->is_video
) {
323 "Track %d in %s is neither video nor audio, skipping\n",
325 av_freep(&files
->files
[files
->nb_files
]);
330 if (files
->audio_file
< 0)
331 files
->audio_file
= files
->nb_files
;
332 files
->nb_audio_files
++;
333 vf
->channels
= st
->codec
->channels
;
334 vf
->sample_rate
= st
->codec
->sample_rate
;
335 if (st
->codec
->codec_id
== AV_CODEC_ID_AAC
) {
339 } else if (st
->codec
->codec_id
== AV_CODEC_ID_WMAPRO
) {
341 vf
->tag
= st
->codec
->codec_tag
;
342 vf
->blocksize
= st
->codec
->block_align
;
344 get_private_data(vf
, st
->codec
);
347 if (files
->video_file
< 0)
348 files
->video_file
= files
->nb_files
;
349 files
->nb_video_files
++;
350 vf
->width
= st
->codec
->width
;
351 vf
->height
= st
->codec
->height
;
352 if (st
->codec
->codec_id
== AV_CODEC_ID_H264
)
354 else if (st
->codec
->codec_id
== AV_CODEC_ID_VC1
)
356 get_video_private_data(vf
, st
->codec
);
362 avformat_close_input(&ctx
);
364 err
= read_mfra(files
, orig_files
, file
, split
);
368 avformat_close_input(&ctx
);
372 static void output_server_manifest(struct VideoFiles
*files
,
373 const char *basename
)
379 snprintf(filename
, sizeof(filename
), "%s.ism", basename
);
380 out
= fopen(filename
, "w");
385 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
386 fprintf(out
, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
387 fprintf(out
, "\t<head>\n");
388 fprintf(out
, "\t\t<meta name=\"clientManifestRelativePath\" "
389 "content=\"%s.ismc\" />\n", basename
);
390 fprintf(out
, "\t</head>\n");
391 fprintf(out
, "\t<body>\n");
392 fprintf(out
, "\t\t<switch>\n");
393 for (i
= 0; i
< files
->nb_files
; i
++) {
394 struct VideoFile
*vf
= files
->files
[i
];
395 const char *type
= vf
->is_video ?
"video" : "audio";
396 fprintf(out
, "\t\t\t<%s src=\"%s\" systemBitrate=\"%d\">\n",
397 type
, vf
->name
, vf
->bitrate
);
398 fprintf(out
, "\t\t\t\t<param name=\"trackID\" value=\"%d\" "
399 "valueType=\"data\" />\n", vf
->track_id
);
400 fprintf(out
, "\t\t\t</%s>\n", type
);
402 fprintf(out
, "\t\t</switch>\n");
403 fprintf(out
, "\t</body>\n");
404 fprintf(out
, "</smil>\n");
408 static void output_client_manifest(struct VideoFiles
*files
,
409 const char *basename
, int split
)
416 snprintf(filename
, sizeof(filename
), "Manifest");
418 snprintf(filename
, sizeof(filename
), "%s.ismc", basename
);
419 out
= fopen(filename
, "w");
424 fprintf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
425 fprintf(out
, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" "
426 "Duration=\"%"PRId64
"\">\n", files
->duration
* 10);
427 if (files
->video_file
>= 0) {
428 struct VideoFile
*vf
= files
->files
[files
->video_file
];
429 struct VideoFile
*first_vf
= vf
;
432 "\t<StreamIndex Type=\"video\" QualityLevels=\"%d\" "
434 "Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n",
435 files
->nb_video_files
, vf
->chunks
);
436 for (i
= 0; i
< files
->nb_files
; i
++) {
437 vf
= files
->files
[i
];
441 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
442 "FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" "
443 "CodecPrivateData=\"",
444 index
, vf
->bitrate
, vf
->fourcc
, vf
->width
, vf
->height
);
445 for (j
= 0; j
< vf
->codec_private_size
; j
++)
446 fprintf(out
, "%02X", vf
->codec_private
[j
]);
447 fprintf(out
, "\" />\n");
449 if (vf
->chunks
!= first_vf
->chunks
)
450 fprintf(stderr
, "Mismatched number of video chunks in %s and %s\n",
451 vf
->name
, first_vf
->name
);
454 for (i
= 0; i
< vf
->chunks
; i
++) {
455 for (j
= files
->video_file
+ 1; j
< files
->nb_files
; j
++) {
456 if (files
->files
[j
]->is_video
&&
457 vf
->offsets
[i
].duration
!= files
->files
[j
]->offsets
[i
].duration
)
458 fprintf(stderr
, "Mismatched duration of video chunk %d in %s and %s\n",
459 i
, vf
->name
, files
->files
[j
]->name
);
461 fprintf(out
, "\t\t<c n=\"%d\" d=\"%d\" />\n", i
,
462 vf
->offsets
[i
].duration
);
464 fprintf(out
, "\t</StreamIndex>\n");
466 if (files
->audio_file
>= 0) {
467 struct VideoFile
*vf
= files
->files
[files
->audio_file
];
468 struct VideoFile
*first_vf
= vf
;
471 "\t<StreamIndex Type=\"audio\" QualityLevels=\"%d\" "
473 "Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n",
474 files
->nb_audio_files
, vf
->chunks
);
475 for (i
= 0; i
< files
->nb_files
; i
++) {
476 vf
= files
->files
[i
];
480 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
481 "FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" "
482 "BitsPerSample=\"16\" PacketSize=\"%d\" "
483 "AudioTag=\"%d\" CodecPrivateData=\"",
484 index
, vf
->bitrate
, vf
->fourcc
, vf
->sample_rate
,
485 vf
->channels
, vf
->blocksize
, vf
->tag
);
486 for (j
= 0; j
< vf
->codec_private_size
; j
++)
487 fprintf(out
, "%02X", vf
->codec_private
[j
]);
488 fprintf(out
, "\" />\n");
490 if (vf
->chunks
!= first_vf
->chunks
)
491 fprintf(stderr
, "Mismatched number of audio chunks in %s and %s\n",
492 vf
->name
, first_vf
->name
);
495 for (i
= 0; i
< vf
->chunks
; i
++) {
496 for (j
= files
->audio_file
+ 1; j
< files
->nb_files
; j
++) {
497 if (files
->files
[j
]->is_audio
&&
498 vf
->offsets
[i
].duration
!= files
->files
[j
]->offsets
[i
].duration
)
499 fprintf(stderr
, "Mismatched duration of audio chunk %d in %s and %s\n",
500 i
, vf
->name
, files
->files
[j
]->name
);
502 fprintf(out
, "\t\t<c n=\"%d\" d=\"%d\" />\n",
503 i
, vf
->offsets
[i
].duration
);
505 fprintf(out
, "\t</StreamIndex>\n");
507 fprintf(out
, "</SmoothStreamingMedia>\n");
511 static void clean_files(struct VideoFiles
*files
)
514 for (i
= 0; i
< files
->nb_files
; i
++) {
515 av_freep(&files
->files
[i
]->codec_private
);
516 av_freep(&files
->files
[i
]->offsets
);
517 av_freep(&files
->files
[i
]);
519 av_freep(&files
->files
);
523 int main(int argc
, char **argv
)
525 const char *basename
= NULL
;
527 struct VideoFiles vf
= { 0, .video_file
= -1, .audio_file
= -1 };
531 for (i
= 1; i
< argc
; i
++) {
532 if (!strcmp(argv
[i
], "-n")) {
533 basename
= argv
[i
+ 1];
535 } else if (!strcmp(argv
[i
], "-split")) {
537 } else if (argv
[i
][0] == '-') {
538 return usage(argv
[0], 1);
540 if (handle_file(&vf
, argv
[i
], split
))
544 if (!vf
.nb_files
|| (!basename
&& !split
))
545 return usage(argv
[0], 1);
548 output_server_manifest(&vf
, basename
);
549 output_client_manifest(&vf
, basename
, split
);