00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035 #include <stdio.h>
00036 #include <string.h>
00037 #include <sys/stat.h>
00038 #ifdef _WIN32
00039 #include <io.h>
00040 #define mkdir(a, b) mkdir(a)
00041 #endif
00042
00043 #include "libavformat/avformat.h"
00044 #include "libavutil/intreadwrite.h"
00045 #include "libavutil/mathematics.h"
00046
00047 static int usage(const char *argv0, int ret)
00048 {
00049 fprintf(stderr, "%s [-split] [-n basename] file1 [file2] ...\n", argv0);
00050 return ret;
00051 }
00052
00053 struct MoofOffset {
00054 int64_t time;
00055 int64_t offset;
00056 int duration;
00057 };
00058
00059 struct VideoFile {
00060 const char *name;
00061 int64_t duration;
00062 int bitrate;
00063 int track_id;
00064 int is_audio, is_video;
00065 int width, height;
00066 int chunks;
00067 int sample_rate, channels;
00068 uint8_t *codec_private;
00069 int codec_private_size;
00070 struct MoofOffset *offsets;
00071 int timescale;
00072 const char *fourcc;
00073 int blocksize;
00074 int tag;
00075 };
00076
00077 struct VideoFiles {
00078 int nb_files;
00079 int64_t duration;
00080 struct VideoFile **files;
00081 int video_file, audio_file;
00082 int nb_video_files, nb_audio_files;
00083 };
00084
00085 static int copy_tag(AVIOContext *in, AVIOContext *out, int32_t tag_name)
00086 {
00087 int32_t size, tag;
00088
00089 size = avio_rb32(in);
00090 tag = avio_rb32(in);
00091 avio_wb32(out, size);
00092 avio_wb32(out, tag);
00093 if (tag != tag_name)
00094 return -1;
00095 size -= 8;
00096 while (size > 0) {
00097 char buf[1024];
00098 int len = FFMIN(sizeof(buf), size);
00099 if (avio_read(in, buf, len) != len)
00100 break;
00101 avio_write(out, buf, len);
00102 size -= len;
00103 }
00104 return 0;
00105 }
00106
00107 static int write_fragment(const char *filename, AVIOContext *in)
00108 {
00109 AVIOContext *out = NULL;
00110 int ret;
00111
00112 if ((ret = avio_open2(&out, filename, AVIO_FLAG_WRITE, NULL, NULL)) < 0)
00113 return ret;
00114 copy_tag(in, out, MKBETAG('m', 'o', 'o', 'f'));
00115 copy_tag(in, out, MKBETAG('m', 'd', 'a', 't'));
00116
00117 avio_flush(out);
00118 avio_close(out);
00119
00120 return ret;
00121 }
00122
00123 static int write_fragments(struct VideoFiles *files, int start_index,
00124 AVIOContext *in)
00125 {
00126 char dirname[100], filename[500];
00127 int i, j;
00128
00129 for (i = start_index; i < files->nb_files; i++) {
00130 struct VideoFile *vf = files->files[i];
00131 const char *type = vf->is_video ? "video" : "audio";
00132 snprintf(dirname, sizeof(dirname), "QualityLevels(%d)", vf->bitrate);
00133 mkdir(dirname, 0777);
00134 for (j = 0; j < vf->chunks; j++) {
00135 snprintf(filename, sizeof(filename), "%s/Fragments(%s=%"PRId64")",
00136 dirname, type, vf->offsets[j].time);
00137 avio_seek(in, vf->offsets[j].offset, SEEK_SET);
00138 write_fragment(filename, in);
00139 }
00140 }
00141 return 0;
00142 }
00143
00144 static int read_tfra(struct VideoFiles *files, int start_index, AVIOContext *f)
00145 {
00146 int ret = AVERROR_EOF, track_id;
00147 int version, fieldlength, i, j;
00148 int64_t pos = avio_tell(f);
00149 uint32_t size = avio_rb32(f);
00150 struct VideoFile *vf = NULL;
00151
00152 if (avio_rb32(f) != MKBETAG('t', 'f', 'r', 'a'))
00153 goto fail;
00154 version = avio_r8(f);
00155 avio_rb24(f);
00156 track_id = avio_rb32(f);
00157 for (i = start_index; i < files->nb_files && !vf; i++)
00158 if (files->files[i]->track_id == track_id)
00159 vf = files->files[i];
00160 if (!vf) {
00161
00162 ret = 0;
00163 goto fail;
00164 }
00165 fieldlength = avio_rb32(f);
00166 vf->chunks = avio_rb32(f);
00167 vf->offsets = av_mallocz(sizeof(*vf->offsets) * vf->chunks);
00168 if (!vf->offsets) {
00169 ret = AVERROR(ENOMEM);
00170 goto fail;
00171 }
00172 for (i = 0; i < vf->chunks; i++) {
00173 if (version == 1) {
00174 vf->offsets[i].time = avio_rb64(f);
00175 vf->offsets[i].offset = avio_rb64(f);
00176 } else {
00177 vf->offsets[i].time = avio_rb32(f);
00178 vf->offsets[i].offset = avio_rb32(f);
00179 }
00180 for (j = 0; j < ((fieldlength >> 4) & 3) + 1; j++)
00181 avio_r8(f);
00182 for (j = 0; j < ((fieldlength >> 2) & 3) + 1; j++)
00183 avio_r8(f);
00184 for (j = 0; j < ((fieldlength >> 0) & 3) + 1; j++)
00185 avio_r8(f);
00186 if (i > 0)
00187 vf->offsets[i - 1].duration = vf->offsets[i].time -
00188 vf->offsets[i - 1].time;
00189 }
00190 if (vf->chunks > 0)
00191 vf->offsets[vf->chunks - 1].duration = vf->duration -
00192 vf->offsets[vf->chunks - 1].time;
00193 ret = 0;
00194
00195 fail:
00196 avio_seek(f, pos + size, SEEK_SET);
00197 return ret;
00198 }
00199
00200 static int read_mfra(struct VideoFiles *files, int start_index,
00201 const char *file, int split)
00202 {
00203 int err = 0;
00204 AVIOContext *f = NULL;
00205 int32_t mfra_size;
00206
00207 if ((err = avio_open2(&f, file, AVIO_FLAG_READ, NULL, NULL)) < 0)
00208 goto fail;
00209 avio_seek(f, avio_size(f) - 4, SEEK_SET);
00210 mfra_size = avio_rb32(f);
00211 avio_seek(f, -mfra_size, SEEK_CUR);
00212 if (avio_rb32(f) != mfra_size)
00213 goto fail;
00214 if (avio_rb32(f) != MKBETAG('m', 'f', 'r', 'a'))
00215 goto fail;
00216 while (!read_tfra(files, start_index, f)) {
00217
00218 }
00219
00220 if (split)
00221 write_fragments(files, start_index, f);
00222
00223 fail:
00224 if (f)
00225 avio_close(f);
00226 return err;
00227 }
00228
00229 static int get_private_data(struct VideoFile *vf, AVCodecContext *codec)
00230 {
00231 vf->codec_private_size = codec->extradata_size;
00232 vf->codec_private = av_mallocz(codec->extradata_size);
00233 if (!vf->codec_private)
00234 return AVERROR(ENOMEM);
00235 memcpy(vf->codec_private, codec->extradata, codec->extradata_size);
00236 return 0;
00237 }
00238
00239 static int get_video_private_data(struct VideoFile *vf, AVCodecContext *codec)
00240 {
00241 AVIOContext *io = NULL;
00242 uint16_t sps_size, pps_size;
00243 int err = AVERROR(EINVAL);
00244
00245 if (codec->codec_id == CODEC_ID_VC1)
00246 return get_private_data(vf, codec);
00247
00248 avio_open_dyn_buf(&io);
00249 if (codec->extradata_size < 11 || codec->extradata[0] != 1)
00250 goto fail;
00251 sps_size = AV_RB16(&codec->extradata[6]);
00252 if (11 + sps_size > codec->extradata_size)
00253 goto fail;
00254 avio_wb32(io, 0x00000001);
00255 avio_write(io, &codec->extradata[8], sps_size);
00256 pps_size = AV_RB16(&codec->extradata[9 + sps_size]);
00257 if (11 + sps_size + pps_size > codec->extradata_size)
00258 goto fail;
00259 avio_wb32(io, 0x00000001);
00260 avio_write(io, &codec->extradata[11 + sps_size], pps_size);
00261 err = 0;
00262
00263 fail:
00264 vf->codec_private_size = avio_close_dyn_buf(io, &vf->codec_private);
00265 return err;
00266 }
00267
00268 static int handle_file(struct VideoFiles *files, const char *file, int split)
00269 {
00270 AVFormatContext *ctx = NULL;
00271 int err = 0, i, orig_files = files->nb_files;
00272 char errbuf[50], *ptr;
00273 struct VideoFile *vf;
00274
00275 err = avformat_open_input(&ctx, file, NULL, NULL);
00276 if (err < 0) {
00277 av_strerror(err, errbuf, sizeof(errbuf));
00278 fprintf(stderr, "Unable to open %s: %s\n", file, errbuf);
00279 return 1;
00280 }
00281
00282 err = avformat_find_stream_info(ctx, NULL);
00283 if (err < 0) {
00284 av_strerror(err, errbuf, sizeof(errbuf));
00285 fprintf(stderr, "Unable to identify %s: %s\n", file, errbuf);
00286 goto fail;
00287 }
00288
00289 if (ctx->nb_streams < 1) {
00290 fprintf(stderr, "No streams found in %s\n", file);
00291 goto fail;
00292 }
00293 if (!files->duration)
00294 files->duration = ctx->duration;
00295
00296 for (i = 0; i < ctx->nb_streams; i++) {
00297 AVStream *st = ctx->streams[i];
00298 vf = av_mallocz(sizeof(*vf));
00299 files->files = av_realloc(files->files,
00300 sizeof(*files->files) * (files->nb_files + 1));
00301 files->files[files->nb_files] = vf;
00302
00303 vf->name = file;
00304 if ((ptr = strrchr(file, '/')) != NULL)
00305 vf->name = ptr + 1;
00306
00307 vf->bitrate = st->codec->bit_rate;
00308 vf->track_id = st->id;
00309 vf->timescale = st->time_base.den;
00310 vf->duration = av_rescale_rnd(ctx->duration, vf->timescale,
00311 AV_TIME_BASE, AV_ROUND_UP);
00312 vf->is_audio = st->codec->codec_type == AVMEDIA_TYPE_AUDIO;
00313 vf->is_video = st->codec->codec_type == AVMEDIA_TYPE_VIDEO;
00314
00315 if (!vf->is_audio && !vf->is_video) {
00316 fprintf(stderr,
00317 "Track %d in %s is neither video nor audio, skipping\n",
00318 vf->track_id, file);
00319 av_freep(&files->files[files->nb_files]);
00320 continue;
00321 }
00322
00323 if (vf->is_audio) {
00324 if (files->audio_file < 0)
00325 files->audio_file = files->nb_files;
00326 files->nb_audio_files++;
00327 vf->channels = st->codec->channels;
00328 vf->sample_rate = st->codec->sample_rate;
00329 if (st->codec->codec_id == CODEC_ID_AAC) {
00330 vf->fourcc = "AACL";
00331 vf->tag = 255;
00332 vf->blocksize = 4;
00333 } else if (st->codec->codec_id == CODEC_ID_WMAPRO) {
00334 vf->fourcc = "WMAP";
00335 vf->tag = st->codec->codec_tag;
00336 vf->blocksize = st->codec->block_align;
00337 }
00338 get_private_data(vf, st->codec);
00339 }
00340 if (vf->is_video) {
00341 if (files->video_file < 0)
00342 files->video_file = files->nb_files;
00343 files->nb_video_files++;
00344 vf->width = st->codec->width;
00345 vf->height = st->codec->height;
00346 if (st->codec->codec_id == CODEC_ID_H264)
00347 vf->fourcc = "H264";
00348 else if (st->codec->codec_id == CODEC_ID_VC1)
00349 vf->fourcc = "WVC1";
00350 get_video_private_data(vf, st->codec);
00351 }
00352
00353 files->nb_files++;
00354 }
00355
00356 avformat_close_input(&ctx);
00357
00358 read_mfra(files, orig_files, file, split);
00359
00360 fail:
00361 if (ctx)
00362 avformat_close_input(&ctx);
00363 return err;
00364 }
00365
00366 static void output_server_manifest(struct VideoFiles *files,
00367 const char *basename)
00368 {
00369 char filename[1000];
00370 FILE *out;
00371 int i;
00372
00373 snprintf(filename, sizeof(filename), "%s.ism", basename);
00374 out = fopen(filename, "w");
00375 if (!out) {
00376 perror(filename);
00377 return;
00378 }
00379 fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
00380 fprintf(out, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
00381 fprintf(out, "\t<head>\n");
00382 fprintf(out, "\t\t<meta name=\"clientManifestRelativePath\" "
00383 "content=\"%s.ismc\" />\n", basename);
00384 fprintf(out, "\t</head>\n");
00385 fprintf(out, "\t<body>\n");
00386 fprintf(out, "\t\t<switch>\n");
00387 for (i = 0; i < files->nb_files; i++) {
00388 struct VideoFile *vf = files->files[i];
00389 const char *type = vf->is_video ? "video" : "audio";
00390 fprintf(out, "\t\t\t<%s src=\"%s\" systemBitrate=\"%d\">\n",
00391 type, vf->name, vf->bitrate);
00392 fprintf(out, "\t\t\t\t<param name=\"trackID\" value=\"%d\" "
00393 "valueType=\"data\" />\n", vf->track_id);
00394 fprintf(out, "\t\t\t</%s>\n", type);
00395 }
00396 fprintf(out, "\t\t</switch>\n");
00397 fprintf(out, "\t</body>\n");
00398 fprintf(out, "</smil>\n");
00399 fclose(out);
00400 }
00401
00402 static void output_client_manifest(struct VideoFiles *files,
00403 const char *basename, int split)
00404 {
00405 char filename[1000];
00406 FILE *out;
00407 int i, j;
00408
00409 if (split)
00410 snprintf(filename, sizeof(filename), "Manifest");
00411 else
00412 snprintf(filename, sizeof(filename), "%s.ismc", basename);
00413 out = fopen(filename, "w");
00414 if (!out) {
00415 perror(filename);
00416 return;
00417 }
00418 fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
00419 fprintf(out, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" "
00420 "Duration=\"%"PRId64 "\">\n", files->duration * 10);
00421 if (files->video_file >= 0) {
00422 struct VideoFile *vf = files->files[files->video_file];
00423 int index = 0;
00424 fprintf(out,
00425 "\t<StreamIndex Type=\"video\" QualityLevels=\"%d\" "
00426 "Chunks=\"%d\" "
00427 "Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n",
00428 files->nb_video_files, vf->chunks);
00429 for (i = 0; i < files->nb_files; i++) {
00430 vf = files->files[i];
00431 if (!vf->is_video)
00432 continue;
00433 fprintf(out,
00434 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
00435 "FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" "
00436 "CodecPrivateData=\"",
00437 index, vf->bitrate, vf->fourcc, vf->width, vf->height);
00438 for (j = 0; j < vf->codec_private_size; j++)
00439 fprintf(out, "%02X", vf->codec_private[j]);
00440 fprintf(out, "\" />\n");
00441 index++;
00442 }
00443 vf = files->files[files->video_file];
00444 for (i = 0; i < vf->chunks; i++)
00445 fprintf(out, "\t\t<c n=\"%d\" d=\"%d\" />\n", i,
00446 vf->offsets[i].duration);
00447 fprintf(out, "\t</StreamIndex>\n");
00448 }
00449 if (files->audio_file >= 0) {
00450 struct VideoFile *vf = files->files[files->audio_file];
00451 int index = 0;
00452 fprintf(out,
00453 "\t<StreamIndex Type=\"audio\" QualityLevels=\"%d\" "
00454 "Chunks=\"%d\" "
00455 "Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n",
00456 files->nb_audio_files, vf->chunks);
00457 for (i = 0; i < files->nb_files; i++) {
00458 vf = files->files[i];
00459 if (!vf->is_audio)
00460 continue;
00461 fprintf(out,
00462 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
00463 "FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" "
00464 "BitsPerSample=\"16\" PacketSize=\"%d\" "
00465 "AudioTag=\"%d\" CodecPrivateData=\"",
00466 index, vf->bitrate, vf->fourcc, vf->sample_rate,
00467 vf->channels, vf->blocksize, vf->tag);
00468 for (j = 0; j < vf->codec_private_size; j++)
00469 fprintf(out, "%02X", vf->codec_private[j]);
00470 fprintf(out, "\" />\n");
00471 index++;
00472 }
00473 vf = files->files[files->audio_file];
00474 for (i = 0; i < vf->chunks; i++)
00475 fprintf(out, "\t\t<c n=\"%d\" d=\"%d\" />\n",
00476 i, vf->offsets[i].duration);
00477 fprintf(out, "\t</StreamIndex>\n");
00478 }
00479 fprintf(out, "</SmoothStreamingMedia>\n");
00480 fclose(out);
00481 }
00482
00483 static void clean_files(struct VideoFiles *files)
00484 {
00485 int i;
00486 for (i = 0; i < files->nb_files; i++) {
00487 av_freep(&files->files[i]->codec_private);
00488 av_freep(&files->files[i]->offsets);
00489 av_freep(&files->files[i]);
00490 }
00491 av_freep(&files->files);
00492 files->nb_files = 0;
00493 }
00494
00495 int main(int argc, char **argv)
00496 {
00497 const char *basename = NULL;
00498 int split = 0, i;
00499 struct VideoFiles vf = { 0, .video_file = -1, .audio_file = -1 };
00500
00501 av_register_all();
00502
00503 for (i = 1; i < argc; i++) {
00504 if (!strcmp(argv[i], "-n")) {
00505 basename = argv[i + 1];
00506 i++;
00507 } else if (!strcmp(argv[i], "-split")) {
00508 split = 1;
00509 } else if (argv[i][0] == '-') {
00510 return usage(argv[0], 1);
00511 } else {
00512 handle_file(&vf, argv[i], split);
00513 }
00514 }
00515 if (!vf.nb_files || (!basename && !split))
00516 return usage(argv[0], 1);
00517
00518 if (!split)
00519 output_server_manifest(&vf, basename);
00520 output_client_manifest(&vf, basename, split);
00521
00522 clean_files(&vf);
00523
00524 return 0;
00525 }