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