| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * qt-faststart.c, v0.2 | ||
| 3 | * by Mike Melanson (melanson@pcisys.net) | ||
| 4 | * This file is placed in the public domain. Use the program however you | ||
| 5 | * see fit. | ||
| 6 | * | ||
| 7 | * This utility rearranges a Quicktime file such that the moov atom | ||
| 8 | * is in front of the data, thus facilitating network streaming. | ||
| 9 | * | ||
| 10 | * To compile this program, start from the base directory from which you | ||
| 11 | * are building FFmpeg and type: | ||
| 12 | * make tools/qt-faststart | ||
| 13 | * The qt-faststart program will be built in the tools/ directory. If you | ||
| 14 | * do not build the program in this manner, correct results are not | ||
| 15 | * guaranteed, particularly on 64-bit platforms. | ||
| 16 | * Invoke the program with: | ||
| 17 | * qt-faststart <infile.mov> <outfile.mov> | ||
| 18 | * | ||
| 19 | * Notes: Quicktime files can come in many configurations of top-level | ||
| 20 | * atoms. This utility stipulates that the very last atom in the file needs | ||
| 21 | * to be a moov atom. When given such a file, this utility will rearrange | ||
| 22 | * the top-level atoms by shifting the moov atom from the back of the file | ||
| 23 | * to the front, and patch the chunk offsets along the way. This utility | ||
| 24 | * presently only operates on uncompressed moov atoms. | ||
| 25 | */ | ||
| 26 | |||
| 27 | #include <stdio.h> | ||
| 28 | #include <stdlib.h> | ||
| 29 | #include <inttypes.h> | ||
| 30 | #include <string.h> | ||
| 31 | #include <limits.h> | ||
| 32 | |||
| 33 | #ifdef __MINGW32__ | ||
| 34 | #undef fseeko | ||
| 35 | #define fseeko(x, y, z) fseeko64(x, y, z) | ||
| 36 | #undef ftello | ||
| 37 | #define ftello(x) ftello64(x) | ||
| 38 | #elif defined(_WIN32) | ||
| 39 | #undef fseeko | ||
| 40 | #define fseeko(x, y, z) _fseeki64(x, y, z) | ||
| 41 | #undef ftello | ||
| 42 | #define ftello(x) _ftelli64(x) | ||
| 43 | #endif | ||
| 44 | |||
| 45 | #define MIN(a,b) ((a) > (b) ? (b) : (a)) | ||
| 46 | |||
| 47 | #define BE_32(x) (((uint32_t)(((uint8_t*)(x))[0]) << 24) | \ | ||
| 48 | (((uint8_t*)(x))[1] << 16) | \ | ||
| 49 | (((uint8_t*)(x))[2] << 8) | \ | ||
| 50 | ((uint8_t*)(x))[3]) | ||
| 51 | |||
| 52 | #define BE_64(x) (((uint64_t)(((uint8_t*)(x))[0]) << 56) | \ | ||
| 53 | ((uint64_t)(((uint8_t*)(x))[1]) << 48) | \ | ||
| 54 | ((uint64_t)(((uint8_t*)(x))[2]) << 40) | \ | ||
| 55 | ((uint64_t)(((uint8_t*)(x))[3]) << 32) | \ | ||
| 56 | ((uint64_t)(((uint8_t*)(x))[4]) << 24) | \ | ||
| 57 | ((uint64_t)(((uint8_t*)(x))[5]) << 16) | \ | ||
| 58 | ((uint64_t)(((uint8_t*)(x))[6]) << 8) | \ | ||
| 59 | ((uint64_t)( (uint8_t*)(x))[7])) | ||
| 60 | |||
| 61 | #define AV_WB32(p, val) { \ | ||
| 62 | ((uint8_t*)(p))[0] = ((val) >> 24) & 0xff; \ | ||
| 63 | ((uint8_t*)(p))[1] = ((val) >> 16) & 0xff; \ | ||
| 64 | ((uint8_t*)(p))[2] = ((val) >> 8) & 0xff; \ | ||
| 65 | ((uint8_t*)(p))[3] = (val) & 0xff; \ | ||
| 66 | } | ||
| 67 | |||
| 68 | #define AV_WB64(p, val) { \ | ||
| 69 | AV_WB32(p, (val) >> 32) \ | ||
| 70 | AV_WB32(p + 4, val) \ | ||
| 71 | } | ||
| 72 | |||
| 73 | #define BE_FOURCC(ch0, ch1, ch2, ch3) \ | ||
| 74 | ( (uint32_t)(unsigned char)(ch3) | \ | ||
| 75 | ((uint32_t)(unsigned char)(ch2) << 8) | \ | ||
| 76 | ((uint32_t)(unsigned char)(ch1) << 16) | \ | ||
| 77 | ((uint32_t)(unsigned char)(ch0) << 24) ) | ||
| 78 | |||
| 79 | #define QT_ATOM BE_FOURCC | ||
| 80 | /* top level atoms */ | ||
| 81 | #define FREE_ATOM QT_ATOM('f', 'r', 'e', 'e') | ||
| 82 | #define JUNK_ATOM QT_ATOM('j', 'u', 'n', 'k') | ||
| 83 | #define MDAT_ATOM QT_ATOM('m', 'd', 'a', 't') | ||
| 84 | #define MOOV_ATOM QT_ATOM('m', 'o', 'o', 'v') | ||
| 85 | #define PNOT_ATOM QT_ATOM('p', 'n', 'o', 't') | ||
| 86 | #define SKIP_ATOM QT_ATOM('s', 'k', 'i', 'p') | ||
| 87 | #define WIDE_ATOM QT_ATOM('w', 'i', 'd', 'e') | ||
| 88 | #define PICT_ATOM QT_ATOM('P', 'I', 'C', 'T') | ||
| 89 | #define FTYP_ATOM QT_ATOM('f', 't', 'y', 'p') | ||
| 90 | #define UUID_ATOM QT_ATOM('u', 'u', 'i', 'd') | ||
| 91 | |||
| 92 | #define CMOV_ATOM QT_ATOM('c', 'm', 'o', 'v') | ||
| 93 | #define TRAK_ATOM QT_ATOM('t', 'r', 'a', 'k') | ||
| 94 | #define MDIA_ATOM QT_ATOM('m', 'd', 'i', 'a') | ||
| 95 | #define MINF_ATOM QT_ATOM('m', 'i', 'n', 'f') | ||
| 96 | #define STBL_ATOM QT_ATOM('s', 't', 'b', 'l') | ||
| 97 | #define STCO_ATOM QT_ATOM('s', 't', 'c', 'o') | ||
| 98 | #define CO64_ATOM QT_ATOM('c', 'o', '6', '4') | ||
| 99 | |||
| 100 | #define ATOM_PREAMBLE_SIZE 8 | ||
| 101 | #define COPY_BUFFER_SIZE 33554432 | ||
| 102 | #define MAX_FTYP_ATOM_SIZE 1048576 | ||
| 103 | |||
| 104 | typedef struct { | ||
| 105 | uint32_t type; | ||
| 106 | uint32_t header_size; | ||
| 107 | uint64_t size; | ||
| 108 | unsigned char *data; | ||
| 109 | } atom_t; | ||
| 110 | |||
| 111 | typedef struct { | ||
| 112 | uint64_t moov_atom_size; | ||
| 113 | uint64_t stco_offset_count; | ||
| 114 | uint64_t stco_data_size; | ||
| 115 | int stco_overflow; | ||
| 116 | uint32_t depth; | ||
| 117 | } update_chunk_offsets_context_t; | ||
| 118 | |||
| 119 | typedef struct { | ||
| 120 | unsigned char *dest; | ||
| 121 | uint64_t original_moov_size; | ||
| 122 | uint64_t new_moov_size; | ||
| 123 | } upgrade_stco_context_t; | ||
| 124 | |||
| 125 | typedef int (*parse_atoms_callback_t)(void *context, atom_t *atom); | ||
| 126 | |||
| 127 | 12 | static int parse_atoms( | |
| 128 | unsigned char *buf, | ||
| 129 | uint64_t size, | ||
| 130 | parse_atoms_callback_t callback, | ||
| 131 | void *context) | ||
| 132 | { | ||
| 133 | 12 | unsigned char *pos = buf; | |
| 134 | 12 | unsigned char *end = pos + size; | |
| 135 | atom_t atom; | ||
| 136 | int ret; | ||
| 137 | |||
| 138 |
2/2✓ Branch 0 taken 38 times.
✓ Branch 1 taken 12 times.
|
50 | while (end - pos >= ATOM_PREAMBLE_SIZE) { |
| 139 | 38 | atom.size = BE_32(pos); | |
| 140 | 38 | atom.type = BE_32(pos + 4); | |
| 141 | 38 | pos += ATOM_PREAMBLE_SIZE; | |
| 142 | 38 | atom.header_size = ATOM_PREAMBLE_SIZE; | |
| 143 | |||
| 144 |
1/3✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
|
38 | switch (atom.size) { |
| 145 | ✗ | case 1: | |
| 146 | ✗ | if (end - pos < 8) { | |
| 147 | ✗ | fprintf(stderr, "not enough room for 64 bit atom size\n"); | |
| 148 | ✗ | return -1; | |
| 149 | } | ||
| 150 | |||
| 151 | ✗ | atom.size = BE_64(pos); | |
| 152 | ✗ | pos += 8; | |
| 153 | ✗ | atom.header_size = ATOM_PREAMBLE_SIZE + 8; | |
| 154 | ✗ | break; | |
| 155 | |||
| 156 | ✗ | case 0: | |
| 157 | ✗ | atom.size = ATOM_PREAMBLE_SIZE + end - pos; | |
| 158 | ✗ | break; | |
| 159 | } | ||
| 160 | |||
| 161 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (atom.size < atom.header_size) { |
| 162 | ✗ | fprintf(stderr, "atom size %"PRIu64" too small\n", atom.size); | |
| 163 | ✗ | return -1; | |
| 164 | } | ||
| 165 | |||
| 166 | 38 | atom.size -= atom.header_size; | |
| 167 | |||
| 168 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (atom.size > end - pos) { |
| 169 | ✗ | fprintf(stderr, "atom size %"PRIu64" too big\n", atom.size); | |
| 170 | ✗ | return -1; | |
| 171 | } | ||
| 172 | |||
| 173 | 38 | atom.data = pos; | |
| 174 | 38 | ret = callback(context, &atom); | |
| 175 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 38 times.
|
38 | if (ret < 0) { |
| 176 | ✗ | return ret; | |
| 177 | } | ||
| 178 | |||
| 179 | 38 | pos += atom.size; | |
| 180 | } | ||
| 181 | |||
| 182 | 12 | return 0; | |
| 183 | } | ||
| 184 | |||
| 185 | 1 | static int update_stco_offsets(update_chunk_offsets_context_t *context, atom_t *atom) | |
| 186 | { | ||
| 187 | uint32_t current_offset; | ||
| 188 | uint32_t offset_count; | ||
| 189 | unsigned char *pos; | ||
| 190 | unsigned char *end; | ||
| 191 | |||
| 192 | 1 | printf(" patching stco atom...\n"); | |
| 193 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (atom->size < 8) { |
| 194 | ✗ | fprintf(stderr, "stco atom size %"PRIu64" too small\n", atom->size); | |
| 195 | ✗ | return -1; | |
| 196 | } | ||
| 197 | |||
| 198 | 1 | offset_count = BE_32(atom->data + 4); | |
| 199 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (offset_count > (atom->size - 8) / 4) { |
| 200 | ✗ | fprintf(stderr, "stco offset count %"PRIu32" too big\n", offset_count); | |
| 201 | ✗ | return -1; | |
| 202 | } | ||
| 203 | |||
| 204 | 1 | context->stco_offset_count += offset_count; | |
| 205 | 1 | context->stco_data_size += atom->size - 8; | |
| 206 | |||
| 207 | 1 | for (pos = atom->data + 8, end = pos + offset_count * 4; | |
| 208 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | pos < end; |
| 209 | 1 | pos += 4) { | |
| 210 | 1 | current_offset = BE_32(pos); | |
| 211 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (current_offset > UINT_MAX - context->moov_atom_size) { |
| 212 | 1 | context->stco_overflow = 1; | |
| 213 | } | ||
| 214 | 1 | current_offset += context->moov_atom_size; | |
| 215 | 1 | AV_WB32(pos, current_offset); | |
| 216 | } | ||
| 217 | |||
| 218 | 1 | return 0; | |
| 219 | } | ||
| 220 | |||
| 221 | ✗ | static int update_co64_offsets(update_chunk_offsets_context_t *context, atom_t *atom) | |
| 222 | { | ||
| 223 | uint64_t current_offset; | ||
| 224 | uint32_t offset_count; | ||
| 225 | unsigned char *pos; | ||
| 226 | unsigned char *end; | ||
| 227 | |||
| 228 | ✗ | printf(" patching co64 atom...\n"); | |
| 229 | ✗ | if (atom->size < 8) { | |
| 230 | ✗ | fprintf(stderr, "co64 atom size %"PRIu64" too small\n", atom->size); | |
| 231 | ✗ | return -1; | |
| 232 | } | ||
| 233 | |||
| 234 | ✗ | offset_count = BE_32(atom->data + 4); | |
| 235 | ✗ | if (offset_count > (atom->size - 8) / 8) { | |
| 236 | ✗ | fprintf(stderr, "co64 offset count %"PRIu32" too big\n", offset_count); | |
| 237 | ✗ | return -1; | |
| 238 | } | ||
| 239 | |||
| 240 | ✗ | for (pos = atom->data + 8, end = pos + offset_count * 8; | |
| 241 | ✗ | pos < end; | |
| 242 | ✗ | pos += 8) { | |
| 243 | ✗ | current_offset = BE_64(pos); | |
| 244 | ✗ | current_offset += context->moov_atom_size; | |
| 245 | ✗ | AV_WB64(pos, current_offset); | |
| 246 | } | ||
| 247 | |||
| 248 | ✗ | return 0; | |
| 249 | } | ||
| 250 | |||
| 251 | 19 | static int update_chunk_offsets_callback(void *ctx, atom_t *atom) | |
| 252 | { | ||
| 253 | 19 | update_chunk_offsets_context_t *context = ctx; | |
| 254 | int ret; | ||
| 255 | |||
| 256 |
3/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 13 times.
|
19 | switch (atom->type) { |
| 257 | 1 | case STCO_ATOM: | |
| 258 | 1 | return update_stco_offsets(context, atom); | |
| 259 | |||
| 260 | ✗ | case CO64_ATOM: | |
| 261 | ✗ | return update_co64_offsets(context, atom); | |
| 262 | |||
| 263 | 5 | case MOOV_ATOM: | |
| 264 | case TRAK_ATOM: | ||
| 265 | case MDIA_ATOM: | ||
| 266 | case MINF_ATOM: | ||
| 267 | case STBL_ATOM: | ||
| 268 | 5 | context->depth++; | |
| 269 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (context->depth > 10) { |
| 270 | ✗ | fprintf(stderr, "atoms too deeply nested\n"); | |
| 271 | ✗ | return -1; | |
| 272 | } | ||
| 273 | |||
| 274 | 5 | ret = parse_atoms( | |
| 275 | atom->data, | ||
| 276 | atom->size, | ||
| 277 | update_chunk_offsets_callback, | ||
| 278 | context); | ||
| 279 | 5 | context->depth--; | |
| 280 | 5 | return ret; | |
| 281 | } | ||
| 282 | |||
| 283 | 13 | return 0; | |
| 284 | } | ||
| 285 | |||
| 286 | 6 | static void set_atom_size(unsigned char *header, uint32_t header_size, uint64_t size) | |
| 287 | { | ||
| 288 |
1/3✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
|
6 | switch (header_size) { |
| 289 | 6 | case 8: | |
| 290 | 6 | AV_WB32(header, size); | |
| 291 | 6 | break; | |
| 292 | |||
| 293 | ✗ | case 16: | |
| 294 | ✗ | AV_WB64(header + 8, size); | |
| 295 | ✗ | break; | |
| 296 | } | ||
| 297 | 6 | } | |
| 298 | |||
| 299 | 1 | static void upgrade_stco_atom(upgrade_stco_context_t *context, atom_t *atom) | |
| 300 | { | ||
| 301 | unsigned char *pos; | ||
| 302 | unsigned char *end; | ||
| 303 | uint64_t new_offset; | ||
| 304 | uint32_t offset_count; | ||
| 305 | uint32_t original_offset; | ||
| 306 | |||
| 307 | /* Note: not performing validations since they were performed on the first pass */ | ||
| 308 | |||
| 309 | 1 | offset_count = BE_32(atom->data + 4); | |
| 310 | |||
| 311 | /* write the header */ | ||
| 312 | 1 | memcpy(context->dest, atom->data - atom->header_size, atom->header_size + 8); | |
| 313 | 1 | AV_WB32(context->dest + 4, CO64_ATOM); | |
| 314 | 1 | set_atom_size(context->dest, atom->header_size, atom->header_size + 8 + offset_count * 8); | |
| 315 | 1 | context->dest += atom->header_size + 8; | |
| 316 | |||
| 317 | /* write the data */ | ||
| 318 | 1 | for (pos = atom->data + 8, end = pos + offset_count * 4; | |
| 319 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | pos < end; |
| 320 | 1 | pos += 4) { | |
| 321 | 1 | original_offset = BE_32(pos) - context->original_moov_size; | |
| 322 | 1 | new_offset = (uint64_t)original_offset + context->new_moov_size; | |
| 323 | 1 | AV_WB64(context->dest, new_offset); | |
| 324 | 1 | context->dest += 8; | |
| 325 | } | ||
| 326 | 1 | } | |
| 327 | |||
| 328 | 19 | static int upgrade_stco_callback(void *ctx, atom_t *atom) | |
| 329 | { | ||
| 330 | 19 | upgrade_stco_context_t *context = ctx; | |
| 331 | unsigned char *start_pos; | ||
| 332 | uint64_t copy_size; | ||
| 333 | |||
| 334 |
3/3✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 13 times.
|
19 | switch (atom->type) { |
| 335 | 1 | case STCO_ATOM: | |
| 336 | 1 | upgrade_stco_atom(context, atom); | |
| 337 | 1 | break; | |
| 338 | |||
| 339 | 5 | case MOOV_ATOM: | |
| 340 | case TRAK_ATOM: | ||
| 341 | case MDIA_ATOM: | ||
| 342 | case MINF_ATOM: | ||
| 343 | case STBL_ATOM: | ||
| 344 | /* write the atom header */ | ||
| 345 | 5 | memcpy(context->dest, atom->data - atom->header_size, atom->header_size); | |
| 346 | 5 | start_pos = context->dest; | |
| 347 | 5 | context->dest += atom->header_size; | |
| 348 | |||
| 349 | /* parse internal atoms*/ | ||
| 350 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
5 | if (parse_atoms( |
| 351 | atom->data, | ||
| 352 | atom->size, | ||
| 353 | upgrade_stco_callback, | ||
| 354 | context) < 0) { | ||
| 355 | ✗ | return -1; | |
| 356 | } | ||
| 357 | |||
| 358 | /* update the atom size */ | ||
| 359 | 5 | set_atom_size(start_pos, atom->header_size, context->dest - start_pos); | |
| 360 | 5 | break; | |
| 361 | |||
| 362 | 13 | default: | |
| 363 | 13 | copy_size = atom->header_size + atom->size; | |
| 364 | 13 | memcpy(context->dest, atom->data - atom->header_size, copy_size); | |
| 365 | 13 | context->dest += copy_size; | |
| 366 | 13 | break; | |
| 367 | } | ||
| 368 | |||
| 369 | 19 | return 0; | |
| 370 | } | ||
| 371 | |||
| 372 | 1 | static int update_moov_atom( | |
| 373 | unsigned char **moov_atom, | ||
| 374 | uint64_t *moov_atom_size) | ||
| 375 | { | ||
| 376 | 1 | update_chunk_offsets_context_t update_context = { 0 }; | |
| 377 | upgrade_stco_context_t upgrade_context; | ||
| 378 | unsigned char *new_moov_atom; | ||
| 379 | |||
| 380 | 1 | update_context.moov_atom_size = *moov_atom_size; | |
| 381 | |||
| 382 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (parse_atoms( |
| 383 | *moov_atom, | ||
| 384 | *moov_atom_size, | ||
| 385 | update_chunk_offsets_callback, | ||
| 386 | &update_context) < 0) { | ||
| 387 | ✗ | return -1; | |
| 388 | } | ||
| 389 | |||
| 390 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!update_context.stco_overflow) { |
| 391 | ✗ | return 0; | |
| 392 | } | ||
| 393 | |||
| 394 | 1 | printf(" upgrading stco atoms to co64...\n"); | |
| 395 | 1 | upgrade_context.new_moov_size = *moov_atom_size + | |
| 396 | 1 | update_context.stco_offset_count * 8 - | |
| 397 | 1 | update_context.stco_data_size; | |
| 398 | |||
| 399 | 1 | new_moov_atom = malloc(upgrade_context.new_moov_size); | |
| 400 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (new_moov_atom == NULL) { |
| 401 | ✗ | fprintf(stderr, "could not allocate %"PRIu64" bytes for updated moov atom\n", | |
| 402 | upgrade_context.new_moov_size); | ||
| 403 | ✗ | return -1; | |
| 404 | } | ||
| 405 | |||
| 406 | 1 | upgrade_context.original_moov_size = *moov_atom_size; | |
| 407 | 1 | upgrade_context.dest = new_moov_atom; | |
| 408 | |||
| 409 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (parse_atoms( |
| 410 | *moov_atom, | ||
| 411 | *moov_atom_size, | ||
| 412 | upgrade_stco_callback, | ||
| 413 | &upgrade_context) < 0) { | ||
| 414 | ✗ | free(new_moov_atom); | |
| 415 | ✗ | return -1; | |
| 416 | } | ||
| 417 | |||
| 418 | 1 | free(*moov_atom); | |
| 419 | 1 | *moov_atom = new_moov_atom; | |
| 420 | 1 | *moov_atom_size = upgrade_context.new_moov_size; | |
| 421 | |||
| 422 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (upgrade_context.dest != *moov_atom + *moov_atom_size) { |
| 423 | ✗ | fprintf(stderr, "unexpected - wrong number of moov bytes written\n"); | |
| 424 | ✗ | return -1; | |
| 425 | } | ||
| 426 | |||
| 427 | 1 | return 0; | |
| 428 | } | ||
| 429 | |||
| 430 | 1 | int main(int argc, char *argv[]) | |
| 431 | { | ||
| 432 | 1 | FILE *infile = NULL; | |
| 433 | 1 | FILE *outfile = NULL; | |
| 434 | unsigned char atom_bytes[ATOM_PREAMBLE_SIZE]; | ||
| 435 | 1 | uint32_t atom_type = 0; | |
| 436 | 1 | uint64_t atom_size = 0; | |
| 437 | 1 | uint64_t atom_offset = 0; | |
| 438 | int64_t last_offset; | ||
| 439 | 1 | unsigned char *moov_atom = NULL; | |
| 440 | 1 | unsigned char *ftyp_atom = NULL; | |
| 441 | uint64_t moov_atom_size; | ||
| 442 | 1 | uint64_t ftyp_atom_size = 0; | |
| 443 | 1 | int64_t start_offset = 0; | |
| 444 | 1 | unsigned char *copy_buffer = NULL; | |
| 445 | int bytes_to_copy; | ||
| 446 | 1 | uint64_t free_size = 0; | |
| 447 | 1 | uint64_t moov_size = 0; | |
| 448 | |||
| 449 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (argc != 3) { |
| 450 | ✗ | printf("Usage: qt-faststart <infile.mov> <outfile.mov>\n" | |
| 451 | "Note: alternatively you can use -movflags +faststart in ffmpeg\n"); | ||
| 452 | ✗ | return 0; | |
| 453 | } | ||
| 454 | |||
| 455 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!strcmp(argv[1], argv[2])) { |
| 456 | ✗ | fprintf(stderr, "input and output files need to be different\n"); | |
| 457 | ✗ | return 1; | |
| 458 | } | ||
| 459 | |||
| 460 | 1 | infile = fopen(argv[1], "rb"); | |
| 461 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!infile) { |
| 462 | ✗ | perror(argv[1]); | |
| 463 | ✗ | goto error_out; | |
| 464 | } | ||
| 465 | |||
| 466 | /* traverse through the atoms in the file to make sure that 'moov' is | ||
| 467 | * at the end */ | ||
| 468 |
1/2✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | while (!feof(infile)) { |
| 469 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
|
5 | if (fread(atom_bytes, ATOM_PREAMBLE_SIZE, 1, infile) != 1) { |
| 470 | 1 | break; | |
| 471 | } | ||
| 472 | 4 | atom_size = BE_32(&atom_bytes[0]); | |
| 473 | 4 | atom_type = BE_32(&atom_bytes[4]); | |
| 474 | |||
| 475 | /* keep ftyp atom */ | ||
| 476 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
|
4 | if (atom_type == FTYP_ATOM) { |
| 477 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (atom_size > MAX_FTYP_ATOM_SIZE) { |
| 478 | ✗ | fprintf(stderr, "ftyp atom size %"PRIu64" too big\n", | |
| 479 | atom_size); | ||
| 480 | ✗ | goto error_out; | |
| 481 | } | ||
| 482 | 1 | ftyp_atom_size = atom_size; | |
| 483 | 1 | free(ftyp_atom); | |
| 484 | 1 | ftyp_atom = malloc(ftyp_atom_size); | |
| 485 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!ftyp_atom) { |
| 486 | ✗ | fprintf(stderr, "could not allocate %"PRIu64" bytes for ftyp atom\n", | |
| 487 | atom_size); | ||
| 488 | ✗ | goto error_out; | |
| 489 | } | ||
| 490 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
2 | if (fseeko(infile, -ATOM_PREAMBLE_SIZE, SEEK_CUR) || |
| 491 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
2 | fread(ftyp_atom, atom_size, 1, infile) != 1 || |
| 492 | 1 | (start_offset = ftello(infile)) < 0) { | |
| 493 | ✗ | perror(argv[1]); | |
| 494 | ✗ | goto error_out; | |
| 495 | } | ||
| 496 | } else { | ||
| 497 | int ret; | ||
| 498 | /* 64-bit special case */ | ||
| 499 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (atom_size == 1) { |
| 500 | ✗ | if (fread(atom_bytes, ATOM_PREAMBLE_SIZE, 1, infile) != 1) { | |
| 501 | ✗ | break; | |
| 502 | } | ||
| 503 | ✗ | atom_size = BE_64(&atom_bytes[0]); | |
| 504 | ✗ | ret = fseeko(infile, atom_size - ATOM_PREAMBLE_SIZE * 2, SEEK_CUR); | |
| 505 | } else { | ||
| 506 | 3 | ret = fseeko(infile, atom_size - ATOM_PREAMBLE_SIZE, SEEK_CUR); | |
| 507 | } | ||
| 508 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (ret) { |
| 509 | ✗ | perror(argv[1]); | |
| 510 | ✗ | goto error_out; | |
| 511 | } | ||
| 512 | } | ||
| 513 | 4 | printf("%c%c%c%c %10"PRIu64" %"PRIu64"\n", | |
| 514 | (atom_type >> 24) & 255, | ||
| 515 | 4 | (atom_type >> 16) & 255, | |
| 516 | 4 | (atom_type >> 8) & 255, | |
| 517 | (atom_type >> 0) & 255, | ||
| 518 | atom_offset, | ||
| 519 | atom_size); | ||
| 520 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | if ((atom_type != FREE_ATOM) && |
| 521 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
|
4 | (atom_type != JUNK_ATOM) && |
| 522 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | (atom_type != MDAT_ATOM) && |
| 523 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | (atom_type != MOOV_ATOM) && |
| 524 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | (atom_type != PNOT_ATOM) && |
| 525 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | (atom_type != SKIP_ATOM) && |
| 526 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | (atom_type != WIDE_ATOM) && |
| 527 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | (atom_type != PICT_ATOM) && |
| 528 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | (atom_type != UUID_ATOM) && |
| 529 | (atom_type != FTYP_ATOM)) { | ||
| 530 | ✗ | fprintf(stderr, "encountered non-QT top-level atom (is this a QuickTime file?)\n"); | |
| 531 | ✗ | break; | |
| 532 | } | ||
| 533 | 4 | atom_offset += atom_size; | |
| 534 | |||
| 535 | /* The atom header is 8 (or 16 bytes), if the atom size (which | ||
| 536 | * includes these 8 or 16 bytes) is less than that, we won't be | ||
| 537 | * able to continue scanning sensibly after this atom, so break. */ | ||
| 538 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (atom_size < 8) |
| 539 | ✗ | break; | |
| 540 | |||
| 541 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
|
4 | if (atom_type == MOOV_ATOM) |
| 542 | 1 | moov_size = atom_size; | |
| 543 | |||
| 544 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
4 | if (moov_size && atom_type == FREE_ATOM) { |
| 545 | ✗ | free_size += atom_size; | |
| 546 | ✗ | atom_type = MOOV_ATOM; | |
| 547 | ✗ | atom_size = moov_size; | |
| 548 | } | ||
| 549 | } | ||
| 550 | |||
| 551 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (atom_type != MOOV_ATOM) { |
| 552 | ✗ | printf("last atom in file was not a moov atom\n"); | |
| 553 | ✗ | free(ftyp_atom); | |
| 554 | ✗ | fclose(infile); | |
| 555 | ✗ | return 0; | |
| 556 | } | ||
| 557 | |||
| 558 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (atom_size < 16) { |
| 559 | ✗ | fprintf(stderr, "bad moov atom size\n"); | |
| 560 | ✗ | goto error_out; | |
| 561 | } | ||
| 562 | |||
| 563 | /* moov atom was, in fact, the last atom in the chunk; load the whole | ||
| 564 | * moov atom */ | ||
| 565 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (fseeko(infile, -(atom_size + free_size), SEEK_END)) { |
| 566 | ✗ | perror(argv[1]); | |
| 567 | ✗ | goto error_out; | |
| 568 | } | ||
| 569 | 1 | last_offset = ftello(infile); | |
| 570 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (last_offset < 0) { |
| 571 | ✗ | perror(argv[1]); | |
| 572 | ✗ | goto error_out; | |
| 573 | } | ||
| 574 | 1 | moov_atom_size = atom_size; | |
| 575 | 1 | moov_atom = malloc(moov_atom_size); | |
| 576 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!moov_atom) { |
| 577 | ✗ | fprintf(stderr, "could not allocate %"PRIu64" bytes for moov atom\n", atom_size); | |
| 578 | ✗ | goto error_out; | |
| 579 | } | ||
| 580 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (fread(moov_atom, atom_size, 1, infile) != 1) { |
| 581 | ✗ | perror(argv[1]); | |
| 582 | ✗ | goto error_out; | |
| 583 | } | ||
| 584 | |||
| 585 | /* this utility does not support compressed atoms yet, so disqualify | ||
| 586 | * files with compressed QT atoms */ | ||
| 587 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (BE_32(&moov_atom[12]) == CMOV_ATOM) { |
| 588 | ✗ | fprintf(stderr, "this utility does not support compressed moov atoms yet\n"); | |
| 589 | ✗ | goto error_out; | |
| 590 | } | ||
| 591 | |||
| 592 | /* close; will be re-opened later */ | ||
| 593 | 1 | fclose(infile); | |
| 594 | 1 | infile = NULL; | |
| 595 | |||
| 596 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (update_moov_atom(&moov_atom, &moov_atom_size) < 0) { |
| 597 | ✗ | goto error_out; | |
| 598 | } | ||
| 599 | |||
| 600 | /* re-open the input file and open the output file */ | ||
| 601 | 1 | infile = fopen(argv[1], "rb"); | |
| 602 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!infile) { |
| 603 | ✗ | perror(argv[1]); | |
| 604 | ✗ | goto error_out; | |
| 605 | } | ||
| 606 | |||
| 607 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (start_offset > 0) { /* seek after ftyp atom */ |
| 608 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (fseeko(infile, start_offset, SEEK_SET)) { |
| 609 | ✗ | perror(argv[1]); | |
| 610 | ✗ | goto error_out; | |
| 611 | } | ||
| 612 | |||
| 613 | 1 | last_offset -= start_offset; | |
| 614 | } | ||
| 615 | |||
| 616 | 1 | outfile = fopen(argv[2], "wb"); | |
| 617 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!outfile) { |
| 618 | ✗ | perror(argv[2]); | |
| 619 | ✗ | goto error_out; | |
| 620 | } | ||
| 621 | |||
| 622 | /* dump the same ftyp atom */ | ||
| 623 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (ftyp_atom_size > 0) { |
| 624 | 1 | printf(" writing ftyp atom...\n"); | |
| 625 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (fwrite(ftyp_atom, ftyp_atom_size, 1, outfile) != 1) { |
| 626 | ✗ | perror(argv[2]); | |
| 627 | ✗ | goto error_out; | |
| 628 | } | ||
| 629 | } | ||
| 630 | |||
| 631 | /* dump the new moov atom */ | ||
| 632 | 1 | printf(" writing moov atom...\n"); | |
| 633 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (fwrite(moov_atom, moov_atom_size, 1, outfile) != 1) { |
| 634 | ✗ | perror(argv[2]); | |
| 635 | ✗ | goto error_out; | |
| 636 | } | ||
| 637 | |||
| 638 | /* copy the remainder of the infile, from offset 0 -> last_offset - 1 */ | ||
| 639 | 1 | bytes_to_copy = MIN(COPY_BUFFER_SIZE, last_offset); | |
| 640 | 1 | copy_buffer = malloc(bytes_to_copy); | |
| 641 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!copy_buffer) { |
| 642 | ✗ | fprintf(stderr, "could not allocate %d bytes for copy_buffer\n", bytes_to_copy); | |
| 643 | ✗ | goto error_out; | |
| 644 | } | ||
| 645 | 1 | printf(" copying rest of file...\n"); | |
| 646 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | while (last_offset) { |
| 647 | 1 | bytes_to_copy = MIN(bytes_to_copy, last_offset); | |
| 648 | |||
| 649 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (fread(copy_buffer, bytes_to_copy, 1, infile) != 1) { |
| 650 | ✗ | perror(argv[1]); | |
| 651 | ✗ | goto error_out; | |
| 652 | } | ||
| 653 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
|
1 | if (fwrite(copy_buffer, bytes_to_copy, 1, outfile) != 1) { |
| 654 | ✗ | perror(argv[2]); | |
| 655 | ✗ | goto error_out; | |
| 656 | } | ||
| 657 | 1 | last_offset -= bytes_to_copy; | |
| 658 | } | ||
| 659 | |||
| 660 | 1 | fclose(infile); | |
| 661 | 1 | fclose(outfile); | |
| 662 | 1 | free(moov_atom); | |
| 663 | 1 | free(ftyp_atom); | |
| 664 | 1 | free(copy_buffer); | |
| 665 | |||
| 666 | 1 | return 0; | |
| 667 | |||
| 668 | ✗ | error_out: | |
| 669 | ✗ | if (infile) | |
| 670 | ✗ | fclose(infile); | |
| 671 | ✗ | if (outfile) | |
| 672 | ✗ | fclose(outfile); | |
| 673 | ✗ | free(moov_atom); | |
| 674 | ✗ | free(ftyp_atom); | |
| 675 | ✗ | free(copy_buffer); | |
| 676 | ✗ | return 1; | |
| 677 | } | ||
| 678 |