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
32#ifdef __MINGW32__
33#define fseeko(x, y, z) fseeko64(x, y, z)
34#define ftello(x)       ftello64(x)
35#elif defined(_WIN32)
36#define fseeko(x, y, z) _fseeki64(x, y, z)
37#define ftello(x)       _ftelli64(x)
38#endif
39
40#define MIN(a,b) ((a) > (b) ? (b) : (a))
41
42#define BE_16(x) ((((uint8_t*)(x))[0] <<  8) | ((uint8_t*)(x))[1])
43
44#define BE_32(x) (((uint32_t)(((uint8_t*)(x))[0]) << 24) |  \
45                             (((uint8_t*)(x))[1]  << 16) |  \
46                             (((uint8_t*)(x))[2]  <<  8) |  \
47                              ((uint8_t*)(x))[3])
48
49#define BE_64(x) (((uint64_t)(((uint8_t*)(x))[0]) << 56) |  \
50                  ((uint64_t)(((uint8_t*)(x))[1]) << 48) |  \
51                  ((uint64_t)(((uint8_t*)(x))[2]) << 40) |  \
52                  ((uint64_t)(((uint8_t*)(x))[3]) << 32) |  \
53                  ((uint64_t)(((uint8_t*)(x))[4]) << 24) |  \
54                  ((uint64_t)(((uint8_t*)(x))[5]) << 16) |  \
55                  ((uint64_t)(((uint8_t*)(x))[6]) <<  8) |  \
56                  ((uint64_t)( (uint8_t*)(x))[7]))
57
58#define BE_FOURCC(ch0, ch1, ch2, ch3)           \
59    ( (uint32_t)(unsigned char)(ch3)        |   \
60     ((uint32_t)(unsigned char)(ch2) <<  8) |   \
61     ((uint32_t)(unsigned char)(ch1) << 16) |   \
62     ((uint32_t)(unsigned char)(ch0) << 24) )
63
64#define QT_ATOM BE_FOURCC
65/* top level atoms */
66#define FREE_ATOM QT_ATOM('f', 'r', 'e', 'e')
67#define JUNK_ATOM QT_ATOM('j', 'u', 'n', 'k')
68#define MDAT_ATOM QT_ATOM('m', 'd', 'a', 't')
69#define MOOV_ATOM QT_ATOM('m', 'o', 'o', 'v')
70#define PNOT_ATOM QT_ATOM('p', 'n', 'o', 't')
71#define SKIP_ATOM QT_ATOM('s', 'k', 'i', 'p')
72#define WIDE_ATOM QT_ATOM('w', 'i', 'd', 'e')
73#define PICT_ATOM QT_ATOM('P', 'I', 'C', 'T')
74#define FTYP_ATOM QT_ATOM('f', 't', 'y', 'p')
75#define UUID_ATOM QT_ATOM('u', 'u', 'i', 'd')
76
77#define CMOV_ATOM QT_ATOM('c', 'm', 'o', 'v')
78#define STCO_ATOM QT_ATOM('s', 't', 'c', 'o')
79#define CO64_ATOM QT_ATOM('c', 'o', '6', '4')
80
81#define ATOM_PREAMBLE_SIZE    8
82#define COPY_BUFFER_SIZE   33554432
83
84int main(int argc, char *argv[])
85{
86    FILE *infile  = NULL;
87    FILE *outfile = NULL;
88    unsigned char atom_bytes[ATOM_PREAMBLE_SIZE];
89    uint32_t atom_type   = 0;
90    uint64_t atom_size   = 0;
91    uint64_t atom_offset = 0;
92    int64_t last_offset;
93    unsigned char *moov_atom = NULL;
94    unsigned char *ftyp_atom = NULL;
95    uint64_t moov_atom_size;
96    uint64_t ftyp_atom_size = 0;
97    uint64_t i, j;
98    uint32_t offset_count;
99    uint64_t current_offset;
100    int64_t start_offset = 0;
101    unsigned char *copy_buffer = NULL;
102    int bytes_to_copy;
103
104    if (argc != 3) {
105        printf("Usage: qt-faststart <infile.mov> <outfile.mov>\n"
106               "Note: alternatively you can use -movflags +faststart in ffmpeg\n");
107        return 0;
108    }
109
110    if (!strcmp(argv[1], argv[2])) {
111        fprintf(stderr, "input and output files need to be different\n");
112        return 1;
113    }
114
115    infile = fopen(argv[1], "rb");
116    if (!infile) {
117        perror(argv[1]);
118        goto error_out;
119    }
120
121    /* traverse through the atoms in the file to make sure that 'moov' is
122     * at the end */
123    while (!feof(infile)) {
124        if (fread(atom_bytes, ATOM_PREAMBLE_SIZE, 1, infile) != 1) {
125            break;
126        }
127        atom_size = BE_32(&atom_bytes[0]);
128        atom_type = BE_32(&atom_bytes[4]);
129
130        /* keep ftyp atom */
131        if (atom_type == FTYP_ATOM) {
132            ftyp_atom_size = atom_size;
133            free(ftyp_atom);
134            ftyp_atom = malloc(ftyp_atom_size);
135            if (!ftyp_atom) {
136                printf("could not allocate %"PRIu64" bytes for ftyp atom\n",
137                       atom_size);
138                goto error_out;
139            }
140            if (fseeko(infile, -ATOM_PREAMBLE_SIZE, SEEK_CUR) ||
141                fread(ftyp_atom, atom_size, 1, infile) != 1 ||
142                (start_offset = ftello(infile)) < 0) {
143                perror(argv[1]);
144                goto error_out;
145            }
146        } else {
147            int ret;
148            /* 64-bit special case */
149            if (atom_size == 1) {
150                if (fread(atom_bytes, ATOM_PREAMBLE_SIZE, 1, infile) != 1) {
151                    break;
152                }
153                atom_size = BE_64(&atom_bytes[0]);
154                ret = fseeko(infile, atom_size - ATOM_PREAMBLE_SIZE * 2, SEEK_CUR);
155            } else {
156                ret = fseeko(infile, atom_size - ATOM_PREAMBLE_SIZE, SEEK_CUR);
157            }
158            if (ret) {
159                perror(argv[1]);
160                goto error_out;
161            }
162        }
163        printf("%c%c%c%c %10"PRIu64" %"PRIu64"\n",
164               (atom_type >> 24) & 255,
165               (atom_type >> 16) & 255,
166               (atom_type >>  8) & 255,
167               (atom_type >>  0) & 255,
168               atom_offset,
169               atom_size);
170        if ((atom_type != FREE_ATOM) &&
171            (atom_type != JUNK_ATOM) &&
172            (atom_type != MDAT_ATOM) &&
173            (atom_type != MOOV_ATOM) &&
174            (atom_type != PNOT_ATOM) &&
175            (atom_type != SKIP_ATOM) &&
176            (atom_type != WIDE_ATOM) &&
177            (atom_type != PICT_ATOM) &&
178            (atom_type != UUID_ATOM) &&
179            (atom_type != FTYP_ATOM)) {
180            printf("encountered non-QT top-level atom (is this a QuickTime file?)\n");
181            break;
182        }
183        atom_offset += atom_size;
184
185        /* The atom header is 8 (or 16 bytes), if the atom size (which
186         * includes these 8 or 16 bytes) is less than that, we won't be
187         * able to continue scanning sensibly after this atom, so break. */
188        if (atom_size < 8)
189            break;
190    }
191
192    if (atom_type != MOOV_ATOM) {
193        printf("last atom in file was not a moov atom\n");
194        free(ftyp_atom);
195        fclose(infile);
196        return 0;
197    }
198
199    /* moov atom was, in fact, the last atom in the chunk; load the whole
200     * moov atom */
201    if (fseeko(infile, -atom_size, SEEK_END)) {
202        perror(argv[1]);
203        goto error_out;
204    }
205    last_offset    = ftello(infile);
206    if (last_offset < 0) {
207        perror(argv[1]);
208        goto error_out;
209    }
210    moov_atom_size = atom_size;
211    moov_atom      = malloc(moov_atom_size);
212    if (!moov_atom) {
213        printf("could not allocate %"PRIu64" bytes for moov atom\n", atom_size);
214        goto error_out;
215    }
216    if (fread(moov_atom, atom_size, 1, infile) != 1) {
217        perror(argv[1]);
218        goto error_out;
219    }
220
221    /* this utility does not support compressed atoms yet, so disqualify
222     * files with compressed QT atoms */
223    if (BE_32(&moov_atom[12]) == CMOV_ATOM) {
224        printf("this utility does not support compressed moov atoms yet\n");
225        goto error_out;
226    }
227
228    /* close; will be re-opened later */
229    fclose(infile);
230    infile = NULL;
231
232    /* crawl through the moov chunk in search of stco or co64 atoms */
233    for (i = 4; i < moov_atom_size - 4; i++) {
234        atom_type = BE_32(&moov_atom[i]);
235        if (atom_type == STCO_ATOM) {
236            printf(" patching stco atom...\n");
237            atom_size = BE_32(&moov_atom[i - 4]);
238            if (i + atom_size - 4 > moov_atom_size) {
239                printf(" bad atom size\n");
240                goto error_out;
241            }
242            offset_count = BE_32(&moov_atom[i + 8]);
243            if (i + 12 + offset_count * UINT64_C(4) > moov_atom_size) {
244                printf(" bad atom size/element count\n");
245                goto error_out;
246            }
247            for (j = 0; j < offset_count; j++) {
248                current_offset  = BE_32(&moov_atom[i + 12 + j * 4]);
249                current_offset += moov_atom_size;
250                moov_atom[i + 12 + j * 4 + 0] = (current_offset >> 24) & 0xFF;
251                moov_atom[i + 12 + j * 4 + 1] = (current_offset >> 16) & 0xFF;
252                moov_atom[i + 12 + j * 4 + 2] = (current_offset >>  8) & 0xFF;
253                moov_atom[i + 12 + j * 4 + 3] = (current_offset >>  0) & 0xFF;
254            }
255            i += atom_size - 4;
256        } else if (atom_type == CO64_ATOM) {
257            printf(" patching co64 atom...\n");
258            atom_size = BE_32(&moov_atom[i - 4]);
259            if (i + atom_size - 4 > moov_atom_size) {
260                printf(" bad atom size\n");
261                goto error_out;
262            }
263            offset_count = BE_32(&moov_atom[i + 8]);
264            if (i + 12 + offset_count * UINT64_C(8) > moov_atom_size) {
265                printf(" bad atom size/element count\n");
266                goto error_out;
267            }
268            for (j = 0; j < offset_count; j++) {
269                current_offset  = BE_64(&moov_atom[i + 12 + j * 8]);
270                current_offset += moov_atom_size;
271                moov_atom[i + 12 + j * 8 + 0] = (current_offset >> 56) & 0xFF;
272                moov_atom[i + 12 + j * 8 + 1] = (current_offset >> 48) & 0xFF;
273                moov_atom[i + 12 + j * 8 + 2] = (current_offset >> 40) & 0xFF;
274                moov_atom[i + 12 + j * 8 + 3] = (current_offset >> 32) & 0xFF;
275                moov_atom[i + 12 + j * 8 + 4] = (current_offset >> 24) & 0xFF;
276                moov_atom[i + 12 + j * 8 + 5] = (current_offset >> 16) & 0xFF;
277                moov_atom[i + 12 + j * 8 + 6] = (current_offset >>  8) & 0xFF;
278                moov_atom[i + 12 + j * 8 + 7] = (current_offset >>  0) & 0xFF;
279            }
280            i += atom_size - 4;
281        }
282    }
283
284    /* re-open the input file and open the output file */
285    infile = fopen(argv[1], "rb");
286    if (!infile) {
287        perror(argv[1]);
288        goto error_out;
289    }
290
291    if (start_offset > 0) { /* seek after ftyp atom */
292        if (fseeko(infile, start_offset, SEEK_SET)) {
293            perror(argv[1]);
294            goto error_out;
295        }
296
297        last_offset -= start_offset;
298    }
299
300    outfile = fopen(argv[2], "wb");
301    if (!outfile) {
302        perror(argv[2]);
303        goto error_out;
304    }
305
306    /* dump the same ftyp atom */
307    if (ftyp_atom_size > 0) {
308        printf(" writing ftyp atom...\n");
309        if (fwrite(ftyp_atom, ftyp_atom_size, 1, outfile) != 1) {
310            perror(argv[2]);
311            goto error_out;
312        }
313    }
314
315    /* dump the new moov atom */
316    printf(" writing moov atom...\n");
317    if (fwrite(moov_atom, moov_atom_size, 1, outfile) != 1) {
318        perror(argv[2]);
319        goto error_out;
320    }
321
322    /* copy the remainder of the infile, from offset 0 -> last_offset - 1 */
323    bytes_to_copy = MIN(COPY_BUFFER_SIZE, last_offset);
324    copy_buffer = malloc(bytes_to_copy);
325    if (!copy_buffer) {
326        printf("could not allocate %d bytes for copy_buffer\n", bytes_to_copy);
327        goto error_out;
328    }
329    printf(" copying rest of file...\n");
330    while (last_offset) {
331        bytes_to_copy = MIN(bytes_to_copy, last_offset);
332
333        if (fread(copy_buffer, bytes_to_copy, 1, infile) != 1) {
334            perror(argv[1]);
335            goto error_out;
336        }
337        if (fwrite(copy_buffer, bytes_to_copy, 1, outfile) != 1) {
338            perror(argv[2]);
339            goto error_out;
340        }
341        last_offset -= bytes_to_copy;
342    }
343
344    fclose(infile);
345    fclose(outfile);
346    free(moov_atom);
347    free(ftyp_atom);
348    free(copy_buffer);
349
350    return 0;
351
352error_out:
353    if (infile)
354        fclose(infile);
355    if (outfile)
356        fclose(outfile);
357    free(moov_atom);
358    free(ftyp_atom);
359    free(copy_buffer);
360    return 1;
361}
362