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 <direct.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 err = AVERROR_INVALIDDATA;
00214 goto fail;
00215 }
00216 if (avio_rb32(f) != MKBETAG('m', 'f', 'r', 'a')) {
00217 err = AVERROR_INVALIDDATA;
00218 goto fail;
00219 }
00220 while (!read_tfra(files, start_index, f)) {
00221
00222 }
00223
00224 if (split)
00225 write_fragments(files, start_index, f);
00226
00227 fail:
00228 if (f)
00229 avio_close(f);
00230 if (err)
00231 fprintf(stderr, "Unable to read the MFRA atom in %s\n", file);
00232 return err;
00233 }
00234
00235 static int get_private_data(struct VideoFile *vf, AVCodecContext *codec)
00236 {
00237 vf->codec_private_size = codec->extradata_size;
00238 vf->codec_private = av_mallocz(codec->extradata_size);
00239 if (!vf->codec_private)
00240 return AVERROR(ENOMEM);
00241 memcpy(vf->codec_private, codec->extradata, codec->extradata_size);
00242 return 0;
00243 }
00244
00245 static int get_video_private_data(struct VideoFile *vf, AVCodecContext *codec)
00246 {
00247 AVIOContext *io = NULL;
00248 uint16_t sps_size, pps_size;
00249 int err = AVERROR(EINVAL);
00250
00251 if (codec->codec_id == AV_CODEC_ID_VC1)
00252 return get_private_data(vf, codec);
00253
00254 if (avio_open_dyn_buf(&io) < 0) {
00255 err = AVERROR(ENOMEM);
00256 goto fail;
00257 }
00258 if (codec->extradata_size < 11 || codec->extradata[0] != 1)
00259 goto fail;
00260 sps_size = AV_RB16(&codec->extradata[6]);
00261 if (11 + sps_size > codec->extradata_size)
00262 goto fail;
00263 avio_wb32(io, 0x00000001);
00264 avio_write(io, &codec->extradata[8], sps_size);
00265 pps_size = AV_RB16(&codec->extradata[9 + sps_size]);
00266 if (11 + sps_size + pps_size > codec->extradata_size)
00267 goto fail;
00268 avio_wb32(io, 0x00000001);
00269 avio_write(io, &codec->extradata[11 + sps_size], pps_size);
00270 err = 0;
00271
00272 fail:
00273 vf->codec_private_size = avio_close_dyn_buf(io, &vf->codec_private);
00274 return err;
00275 }
00276
00277 static int handle_file(struct VideoFiles *files, const char *file, int split)
00278 {
00279 AVFormatContext *ctx = NULL;
00280 int err = 0, i, orig_files = files->nb_files;
00281 char errbuf[50], *ptr;
00282 struct VideoFile *vf;
00283
00284 err = avformat_open_input(&ctx, file, NULL, NULL);
00285 if (err < 0) {
00286 av_strerror(err, errbuf, sizeof(errbuf));
00287 fprintf(stderr, "Unable to open %s: %s\n", file, errbuf);
00288 return 1;
00289 }
00290
00291 err = avformat_find_stream_info(ctx, NULL);
00292 if (err < 0) {
00293 av_strerror(err, errbuf, sizeof(errbuf));
00294 fprintf(stderr, "Unable to identify %s: %s\n", file, errbuf);
00295 goto fail;
00296 }
00297
00298 if (ctx->nb_streams < 1) {
00299 fprintf(stderr, "No streams found in %s\n", file);
00300 goto fail;
00301 }
00302 if (!files->duration)
00303 files->duration = ctx->duration;
00304
00305 for (i = 0; i < ctx->nb_streams; i++) {
00306 AVStream *st = ctx->streams[i];
00307 vf = av_mallocz(sizeof(*vf));
00308 files->files = av_realloc(files->files,
00309 sizeof(*files->files) * (files->nb_files + 1));
00310 files->files[files->nb_files] = vf;
00311
00312 vf->name = file;
00313 if ((ptr = strrchr(file, '/')) != NULL)
00314 vf->name = ptr + 1;
00315
00316 vf->bitrate = st->codec->bit_rate;
00317 vf->track_id = st->id;
00318 vf->timescale = st->time_base.den;
00319 vf->duration = av_rescale_rnd(ctx->duration, vf->timescale,
00320 AV_TIME_BASE, AV_ROUND_UP);
00321 vf->is_audio = st->codec->codec_type == AVMEDIA_TYPE_AUDIO;
00322 vf->is_video = st->codec->codec_type == AVMEDIA_TYPE_VIDEO;
00323
00324 if (!vf->is_audio && !vf->is_video) {
00325 fprintf(stderr,
00326 "Track %d in %s is neither video nor audio, skipping\n",
00327 vf->track_id, file);
00328 av_freep(&files->files[files->nb_files]);
00329 continue;
00330 }
00331
00332 if (vf->is_audio) {
00333 if (files->audio_file < 0)
00334 files->audio_file = files->nb_files;
00335 files->nb_audio_files++;
00336 vf->channels = st->codec->channels;
00337 vf->sample_rate = st->codec->sample_rate;
00338 if (st->codec->codec_id == AV_CODEC_ID_AAC) {
00339 vf->fourcc = "AACL";
00340 vf->tag = 255;
00341 vf->blocksize = 4;
00342 } else if (st->codec->codec_id == AV_CODEC_ID_WMAPRO) {
00343 vf->fourcc = "WMAP";
00344 vf->tag = st->codec->codec_tag;
00345 vf->blocksize = st->codec->block_align;
00346 }
00347 get_private_data(vf, st->codec);
00348 }
00349 if (vf->is_video) {
00350 if (files->video_file < 0)
00351 files->video_file = files->nb_files;
00352 files->nb_video_files++;
00353 vf->width = st->codec->width;
00354 vf->height = st->codec->height;
00355 if (st->codec->codec_id == AV_CODEC_ID_H264)
00356 vf->fourcc = "H264";
00357 else if (st->codec->codec_id == AV_CODEC_ID_VC1)
00358 vf->fourcc = "WVC1";
00359 get_video_private_data(vf, st->codec);
00360 }
00361
00362 files->nb_files++;
00363 }
00364
00365 avformat_close_input(&ctx);
00366
00367 err = read_mfra(files, orig_files, file, split);
00368
00369 fail:
00370 if (ctx)
00371 avformat_close_input(&ctx);
00372 return err;
00373 }
00374
00375 static void output_server_manifest(struct VideoFiles *files,
00376 const char *basename)
00377 {
00378 char filename[1000];
00379 FILE *out;
00380 int i;
00381
00382 snprintf(filename, sizeof(filename), "%s.ism", basename);
00383 out = fopen(filename, "w");
00384 if (!out) {
00385 perror(filename);
00386 return;
00387 }
00388 fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
00389 fprintf(out, "<smil xmlns=\"http://www.w3.org/2001/SMIL20/Language\">\n");
00390 fprintf(out, "\t<head>\n");
00391 fprintf(out, "\t\t<meta name=\"clientManifestRelativePath\" "
00392 "content=\"%s.ismc\" />\n", basename);
00393 fprintf(out, "\t</head>\n");
00394 fprintf(out, "\t<body>\n");
00395 fprintf(out, "\t\t<switch>\n");
00396 for (i = 0; i < files->nb_files; i++) {
00397 struct VideoFile *vf = files->files[i];
00398 const char *type = vf->is_video ? "video" : "audio";
00399 fprintf(out, "\t\t\t<%s src=\"%s\" systemBitrate=\"%d\">\n",
00400 type, vf->name, vf->bitrate);
00401 fprintf(out, "\t\t\t\t<param name=\"trackID\" value=\"%d\" "
00402 "valueType=\"data\" />\n", vf->track_id);
00403 fprintf(out, "\t\t\t</%s>\n", type);
00404 }
00405 fprintf(out, "\t\t</switch>\n");
00406 fprintf(out, "\t</body>\n");
00407 fprintf(out, "</smil>\n");
00408 fclose(out);
00409 }
00410
00411 static void output_client_manifest(struct VideoFiles *files,
00412 const char *basename, int split)
00413 {
00414 char filename[1000];
00415 FILE *out;
00416 int i, j;
00417
00418 if (split)
00419 snprintf(filename, sizeof(filename), "Manifest");
00420 else
00421 snprintf(filename, sizeof(filename), "%s.ismc", basename);
00422 out = fopen(filename, "w");
00423 if (!out) {
00424 perror(filename);
00425 return;
00426 }
00427 fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
00428 fprintf(out, "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" "
00429 "Duration=\"%"PRId64 "\">\n", files->duration * 10);
00430 if (files->video_file >= 0) {
00431 struct VideoFile *vf = files->files[files->video_file];
00432 struct VideoFile *first_vf = vf;
00433 int index = 0;
00434 fprintf(out,
00435 "\t<StreamIndex Type=\"video\" QualityLevels=\"%d\" "
00436 "Chunks=\"%d\" "
00437 "Url=\"QualityLevels({bitrate})/Fragments(video={start time})\">\n",
00438 files->nb_video_files, vf->chunks);
00439 for (i = 0; i < files->nb_files; i++) {
00440 vf = files->files[i];
00441 if (!vf->is_video)
00442 continue;
00443 fprintf(out,
00444 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
00445 "FourCC=\"%s\" MaxWidth=\"%d\" MaxHeight=\"%d\" "
00446 "CodecPrivateData=\"",
00447 index, vf->bitrate, vf->fourcc, vf->width, vf->height);
00448 for (j = 0; j < vf->codec_private_size; j++)
00449 fprintf(out, "%02X", vf->codec_private[j]);
00450 fprintf(out, "\" />\n");
00451 index++;
00452 if (vf->chunks != first_vf->chunks)
00453 fprintf(stderr, "Mismatched number of video chunks in %s and %s\n",
00454 vf->name, first_vf->name);
00455 }
00456 vf = first_vf;
00457 for (i = 0; i < vf->chunks; i++) {
00458 for (j = files->video_file + 1; j < files->nb_files; j++) {
00459 if (files->files[j]->is_video &&
00460 vf->offsets[i].duration != files->files[j]->offsets[i].duration)
00461 fprintf(stderr, "Mismatched duration of video chunk %d in %s and %s\n",
00462 i, vf->name, files->files[j]->name);
00463 }
00464 fprintf(out, "\t\t<c n=\"%d\" d=\"%d\" />\n", i,
00465 vf->offsets[i].duration);
00466 }
00467 fprintf(out, "\t</StreamIndex>\n");
00468 }
00469 if (files->audio_file >= 0) {
00470 struct VideoFile *vf = files->files[files->audio_file];
00471 struct VideoFile *first_vf = vf;
00472 int index = 0;
00473 fprintf(out,
00474 "\t<StreamIndex Type=\"audio\" QualityLevels=\"%d\" "
00475 "Chunks=\"%d\" "
00476 "Url=\"QualityLevels({bitrate})/Fragments(audio={start time})\">\n",
00477 files->nb_audio_files, vf->chunks);
00478 for (i = 0; i < files->nb_files; i++) {
00479 vf = files->files[i];
00480 if (!vf->is_audio)
00481 continue;
00482 fprintf(out,
00483 "\t\t<QualityLevel Index=\"%d\" Bitrate=\"%d\" "
00484 "FourCC=\"%s\" SamplingRate=\"%d\" Channels=\"%d\" "
00485 "BitsPerSample=\"16\" PacketSize=\"%d\" "
00486 "AudioTag=\"%d\" CodecPrivateData=\"",
00487 index, vf->bitrate, vf->fourcc, vf->sample_rate,
00488 vf->channels, vf->blocksize, vf->tag);
00489 for (j = 0; j < vf->codec_private_size; j++)
00490 fprintf(out, "%02X", vf->codec_private[j]);
00491 fprintf(out, "\" />\n");
00492 index++;
00493 if (vf->chunks != first_vf->chunks)
00494 fprintf(stderr, "Mismatched number of audio chunks in %s and %s\n",
00495 vf->name, first_vf->name);
00496 }
00497 vf = first_vf;
00498 for (i = 0; i < vf->chunks; i++) {
00499 for (j = files->audio_file + 1; j < files->nb_files; j++) {
00500 if (files->files[j]->is_audio &&
00501 vf->offsets[i].duration != files->files[j]->offsets[i].duration)
00502 fprintf(stderr, "Mismatched duration of audio chunk %d in %s and %s\n",
00503 i, vf->name, files->files[j]->name);
00504 }
00505 fprintf(out, "\t\t<c n=\"%d\" d=\"%d\" />\n",
00506 i, vf->offsets[i].duration);
00507 }
00508 fprintf(out, "\t</StreamIndex>\n");
00509 }
00510 fprintf(out, "</SmoothStreamingMedia>\n");
00511 fclose(out);
00512 }
00513
00514 static void clean_files(struct VideoFiles *files)
00515 {
00516 int i;
00517 for (i = 0; i < files->nb_files; i++) {
00518 av_freep(&files->files[i]->codec_private);
00519 av_freep(&files->files[i]->offsets);
00520 av_freep(&files->files[i]);
00521 }
00522 av_freep(&files->files);
00523 files->nb_files = 0;
00524 }
00525
00526 int main(int argc, char **argv)
00527 {
00528 const char *basename = NULL;
00529 int split = 0, i;
00530 struct VideoFiles vf = { 0, .video_file = -1, .audio_file = -1 };
00531
00532 av_register_all();
00533
00534 for (i = 1; i < argc; i++) {
00535 if (!strcmp(argv[i], "-n")) {
00536 basename = argv[i + 1];
00537 i++;
00538 } else if (!strcmp(argv[i], "-split")) {
00539 split = 1;
00540 } else if (argv[i][0] == '-') {
00541 return usage(argv[0], 1);
00542 } else {
00543 if (handle_file(&vf, argv[i], split))
00544 return 1;
00545 }
00546 }
00547 if (!vf.nb_files || (!basename && !split))
00548 return usage(argv[0], 1);
00549
00550 if (!split)
00551 output_server_manifest(&vf, basename);
00552 output_client_manifest(&vf, basename, split);
00553
00554 clean_files(&vf);
00555
00556 return 0;
00557 }