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