r125: This commit was manufactured by cvs2svn to create tag 'r1_1_7-last'.
[cinelerra_cv/mob.git] / hvirtual / quicktime / recover.c
blobdda9e72e03cf5e38c0b431e3ada124d820a7fdf9
1 #include "funcprotos.h"
2 #include "quicktime.h"
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <sys/stat.h>
8 #include <sys/time.h>
9 #include <stdint.h>
16 Face it kids, Linux crashes. Regardless of how good the free software
17 model is, you still need to run on unreliable hardware and to get any
18 hardware support at all you need to compete with McRosoft on features.
19 Ever since RedHat started trying to copy McRosoft's every move,
20 reliability has been fickle at best.
22 The X server crashes, the filesystem crashes, the network crashes, the
23 sound driver crashes, the video driver crashes. Signal handlers only
24 handle application failures but most of the crashes are complete system
25 lockups.
27 This utility should read through a truncated movie file and put a
28 header on it, no matter what immitated McRosoft feature caused the
29 crash. It only handles JPEG encoded with jpeg-6b and MJPEG encoded
30 with the BUZ driver with PCM audio.
40 #define FSEEK fseeko64
43 #define WIDTH 720
44 #define HEIGHT 480
45 #define FRAMERATE (double)30000/1001
46 #define CHANNELS 2
47 #define SAMPLERATE 48000
48 #define BITS 16
49 #define TEMP_FILE "/tmp/temp.mov"
50 #define VCODEC QUICKTIME_MJPA
51 //#define VCODEC QUICKTIME_JPEG
57 #define SEARCH_FRAGMENT (int64_t)0x100000
58 //#define SEARCH_PAD 8
59 #define SEARCH_PAD 16
62 #define GOT_NOTHING 0
63 #define IN_FIELD1 1
64 #define GOT_FIELD1 2
65 #define IN_FIELD2 3
66 #define GOT_FIELD2 4
67 #define GOT_AUDIO 5
68 #define GOT_IMAGE_START 6
69 #define GOT_IMAGE_END 7
71 // Table utilities
72 #define NEW_TABLE(ptr, size, allocation) \
73 { \
74 (ptr) = 0; \
75 (size) = 0; \
76 (allocation) = 0; \
79 #define APPEND_TABLE(ptr, size, allocation, value) \
80 { \
81 if((allocation) <= (size)) \
82 { \
83 if(!(allocation)) \
84 (allocation) = 1024; \
85 else \
86 (allocation) *= 2; \
87 int64_t *new_table = calloc(1, sizeof(int64_t) * (allocation)); \
88 memcpy(new_table, (ptr), sizeof(int64_t) * (size)); \
89 free((ptr)); \
90 (ptr) = new_table; \
91 } \
92 (ptr)[(size)] = (value); \
93 (size)++; \
98 int main(int argc, char *argv[])
100 FILE *in;
101 FILE *temp;
102 quicktime_t *out;
103 int64_t current_byte, ftell_byte;
104 int64_t jpeg_end;
105 int64_t audio_start = 0, audio_end = 0;
106 unsigned char *search_buffer = calloc(1, SEARCH_FRAGMENT);
107 unsigned char *copy_buffer = 0;
108 int i;
109 int64_t file_size;
110 struct stat status;
111 unsigned char data[8];
112 struct stat ostat;
113 int fields = 1;
114 time_t current_time = time(0);
115 time_t prev_time = 0;
116 int jpeg_header_offset;
117 int64_t field1_offset = 0;
118 int64_t field2_offset = 0;
119 int64_t image_start = 0;
120 int64_t image_end = 0;
121 int update_time = 0;
122 int state = GOT_NOTHING;
123 char *in_path;
124 int audio_frame;
125 int total_samples;
126 int field;
128 // Value taken from Cinelerra preferences
129 int audio_chunk = 131072;
132 int64_t *start_table;
133 int start_size;
134 int start_allocation;
135 int64_t *end_table;
136 int end_size;
137 int end_allocation;
138 int64_t *field_table;
139 int64_t field_size;
140 int64_t field_allocation;
142 if(argc < 2)
144 printf("Recover JPEG and PCM audio in a corrupted movie.\n"
145 "Usage: recover [options] <input>\n"
146 "Options:\n"
147 " -b samples number of samples in an audio chunk (%d)\n"
148 "\n",
149 audio_chunk);
150 exit(1);
153 for(i = 1; i < argc; i++)
155 if(!strcmp(argv[i], "-b"))
157 if(i + 1 < argc)
159 audio_chunk = atol(argv[i + 1]);
160 i++;
161 if(audio_chunk <= 0)
163 printf("Sample count for -b is out of range.\n");
164 exit(1);
167 else
169 printf("-b needs a sample count.\n");
170 exit(1);
173 else
175 in_path = argv[i];
180 // Dump codec settings
181 printf("Codec settings:\n"
182 " WIDTH=%d HEIGHT=%d\n"
183 " FRAMERATE=%.2f\n"
184 " CHANNELS=%d\n"
185 " SAMPLERATE=%d\n"
186 " BITS=%d\n"
187 " audio chunk=%d\n"
188 " VCODEC=\"%s\"\n",
189 WIDTH,
190 HEIGHT,
191 FRAMERATE,
192 CHANNELS,
193 SAMPLERATE,
194 BITS,
195 audio_chunk,
196 VCODEC);
200 in = fopen(in_path, "rb+");
201 out = quicktime_open(TEMP_FILE, 0, 1);
203 if(!in)
205 perror("open input");
206 exit(1);
208 if(!out)
210 perror("open temp");
211 exit(1);
214 quicktime_set_audio(out,
215 CHANNELS,
216 SAMPLERATE,
217 BITS,
218 QUICKTIME_TWOS);
219 quicktime_set_video(out,
221 WIDTH,
222 HEIGHT,
223 FRAMERATE,
224 VCODEC);
225 audio_start = (int64_t)0x10;
226 ftell_byte = 0;
228 if(fstat(fileno(in), &status))
229 perror("get_file_length fstat:");
230 file_size = status.st_size;
233 NEW_TABLE(start_table, start_size, start_allocation)
234 NEW_TABLE(end_table, end_size, end_allocation)
235 NEW_TABLE(field_table, field_size, field_allocation)
238 // Get the field count
239 if(!memcmp(VCODEC, QUICKTIME_MJPA, 4))
241 fields = 2;
243 else
245 fields = 1;
248 audio_frame = BITS * CHANNELS / 8;
250 // Tabulate the start and end of all the JPEG images.
251 // This search is intended to be as simple as possible, reserving more
252 // complicated operations for a table pass.
253 while(ftell_byte < file_size)
255 current_byte = ftell_byte;
256 fread(search_buffer, SEARCH_FRAGMENT, 1, in);
257 ftell_byte = current_byte + SEARCH_FRAGMENT - SEARCH_PAD;
258 FSEEK(in, ftell_byte, SEEK_SET);
260 for(i = 0; i < SEARCH_FRAGMENT - SEARCH_PAD; i++)
262 // Search for image start
263 if(state == GOT_NOTHING)
265 if(search_buffer[i] == 0xff &&
266 search_buffer[i + 1] == 0xd8 &&
267 search_buffer[i + 2] == 0xff &&
268 search_buffer[i + 3] == 0xe1 &&
269 search_buffer[i + 10] == 'm' &&
270 search_buffer[i + 11] == 'j' &&
271 search_buffer[i + 12] == 'p' &&
272 search_buffer[i + 13] == 'g')
274 state = GOT_IMAGE_START;
275 image_start = current_byte + i;
277 // Determine the field
278 if(fields == 2)
280 // Next field offset is nonzero in first field
281 if(search_buffer[i + 22] != 0 ||
282 search_buffer[i + 23] != 0 ||
283 search_buffer[i + 24] != 0 ||
284 search_buffer[i + 25] != 0)
286 field = 0;
288 else
290 field = 1;
292 APPEND_TABLE(field_table, field_size, field_allocation, field)
295 else
296 if(search_buffer[i] == 0xff &&
297 search_buffer[i + 1] == 0xd8 &&
298 search_buffer[i + 2] == 0xff &&
299 search_buffer[i + 3] == 0xe0 &&
300 search_buffer[i + 6] == 'J' &&
301 search_buffer[i + 7] == 'F' &&
302 search_buffer[i + 8] == 'I' &&
303 search_buffer[i + 9] == 'F')
305 state = GOT_IMAGE_START;
306 image_start = current_byte + i;
309 else
310 // Search for image end
311 if(state == GOT_IMAGE_START)
313 if(search_buffer[i] == 0xff &&
314 search_buffer[i + 1] == 0xd9)
316 // ffd9 sometimes occurs inside the mjpg tag
317 if(current_byte + i - image_start > 0x2a)
319 state = GOT_NOTHING;
320 // Put it in the table
321 image_end = current_byte + i + 2;
323 // An image may have been lost due to encoding errors but we can't do anything
324 // because the audio may by misaligned. Use the extract utility to get the audio.
325 if(image_end - image_start > audio_chunk * audio_frame)
327 printf("Possibly lost image between %llx and %llx\n",
328 image_start,
329 image_end);
330 // Put in fake image
332 * APPEND_TABLE(start_table, start_size, start_allocation, image_start)
333 * APPEND_TABLE(end_table, end_size, end_allocation, image_start + 1024)
334 * APPEND_TABLE(start_table, start_size, start_allocation, image_end - 1024)
335 * APPEND_TABLE(end_table, end_size, end_allocation, image_end)
339 APPEND_TABLE(start_table, start_size, start_allocation, image_start)
340 APPEND_TABLE(end_table, end_size, end_allocation, image_end)
342 //printf("%d %llx - %llx\n", start_size, image_start, image_end - image_start);
344 if(!(start_size % 100))
346 printf("Got %d frames. %d%%\r",
347 start_size,
348 current_byte * (int64_t)100 / file_size);
349 fflush(stdout);
360 // With the image table complete,
361 // write chunk table from the gaps in the image table
362 total_samples = 0;
363 for(i = 1; i < start_size; i++)
365 int64_t next_image_start = start_table[i];
366 int64_t prev_image_end = end_table[i - 1];
368 // Got a chunk
369 if(next_image_start - prev_image_end >= audio_chunk * audio_frame)
371 long samples = (next_image_start - prev_image_end) / audio_frame;
372 quicktime_update_tables(out,
373 out->atracks[0].track,
374 prev_image_end,
375 out->atracks[0].current_chunk,
376 out->atracks[0].current_position,
377 samples,
379 out->atracks[0].current_position += samples;
380 out->atracks[0].current_chunk++;
381 total_samples += samples;
389 // Put image table in movie
390 printf("Got %d frames %d samples total.\n", start_size, total_samples);
391 for(i = 0; i < start_size - fields; i += fields)
393 // Got a field out of order. Skip just 1 image instead of 2.
394 if(fields == 2 && field_table[i] != 0)
396 printf("Got field out of order at 0x%llx\n", start_table[i]);
397 i--;
399 else
401 quicktime_update_tables(out,
402 out->vtracks[0].track,
403 start_table[i],
404 out->vtracks[0].current_chunk,
405 out->vtracks[0].current_position,
407 end_table[i + fields - 1] - start_table[i]);
408 out->vtracks[0].current_position++;
409 out->vtracks[0].current_chunk++;
423 #if 0
426 if(!memcmp(VCODEC, QUICKTIME_MJPA, 4))
428 fields = 2;
429 jpeg_header_offset = 46;
431 else
433 jpeg_header_offset = 2;
436 while(ftell_byte < file_size)
438 // Search forward for JFIF
439 current_byte = ftell_byte;
440 fread(search_buffer, SEARCH_FRAGMENT, 1, in);
441 ftell_byte = current_byte + SEARCH_FRAGMENT - SEARCH_PAD;
442 FSEEK(in, ftell_byte, SEEK_SET);
444 for(i = 0; i < SEARCH_FRAGMENT - SEARCH_PAD; i++)
446 update_time = 0;
448 // Software compression
449 if(search_buffer[i] == 0xff &&
450 search_buffer[i + 1] == 0xe0 &&
451 search_buffer[i + 4] == 'J' &&
452 search_buffer[i + 5] == 'F' &&
453 search_buffer[i + 6] == 'I' &&
454 search_buffer[i + 7] == 'F')
456 if(state == GOT_NOTHING)
458 audio_end = field1_offset = current_byte + i - jpeg_header_offset;
459 state = IN_FIELD1;
461 else
462 if(state == GOT_FIELD1)
464 field2_offset = current_byte + i - jpeg_header_offset;
465 state = IN_FIELD2;
468 else
469 // BUZ driver
470 if(search_buffer[i] == 0xff &&
471 search_buffer[i + 1] == 0xe1 &&
472 search_buffer[i + 8] == 'm' &&
473 search_buffer[i + 9] == 'j' &&
474 search_buffer[i + 10] == 'p' &&
475 search_buffer[i + 11] == 'g')
477 if(state == GOT_NOTHING)
479 audio_end = field1_offset = current_byte + i - 2;
480 state = IN_FIELD1;
482 else
483 if(state == GOT_FIELD1)
485 field2_offset = current_byte + i - 2;
486 state = IN_FIELD2;
489 else
490 if(search_buffer[i] == 0xff &&
491 search_buffer[i + 1] == 0xd9)
493 int got_eoi = 0;
495 // ffd9 sometimes occurs inside the mjpg tag
496 if(state == IN_FIELD1 &&
497 current_byte + i - field1_offset > 0x2a)
499 state = GOT_FIELD1;
500 got_eoi = 1;
502 else
503 if(state == IN_FIELD2 &&
504 current_byte + i - field2_offset > 0x2a)
506 state = GOT_FIELD2;
507 got_eoi = 1;
510 if(got_eoi)
512 int j, got_ff = 0;
513 // BUZ driver puts padding in
514 for(j = 2; j < 8; j++)
516 if(search_buffer[i + j] != 0xff)
518 // Got next frame
519 if(search_buffer[i + j] == 0xd8 && got_ff)
521 audio_start = jpeg_end = current_byte + i + j - 1;
523 else
524 // Got end of video chunk
526 audio_start = jpeg_end = current_byte + i + j;
528 break;
530 got_ff = 1;
536 // Got video frame
537 if((fields == 2 && state == GOT_FIELD2) ||
538 (fields == 1 && state == GOT_FIELD1))
540 quicktime_update_tables(out,
541 out->vtracks[0].track,
542 field1_offset,
543 out->vtracks[0].current_chunk,
544 out->vtracks[0].current_position,
546 jpeg_end - field1_offset);
547 out->vtracks[0].current_position++;
548 out->vtracks[0].current_chunk++;
549 //printf("video chunk %d %d\n", found_jfif, out->vtracks[0].current_position);
550 update_time = 1;
551 state = GOT_NOTHING;
553 else
554 // Got an audio chunk
555 if(audio_end - audio_start > 0)
557 if(audio_end - audio_start > 8)
559 // Audio chunk needs to end on the start of a frame but it needs to
560 // start a multiple of frame_size from the end. The BUZ driver generates
561 // arbitrary padding on the end of frames, so we don't know where the
562 // audio starts.
563 int frame_size = CHANNELS * BITS / 8;
564 int chunk_size = audio_end - audio_start;
565 chunk_size = (int)((chunk_size + frame_size - 1) / frame_size) * frame_size;
566 audio_start = audio_end - chunk_size;
567 long samples = (audio_end - audio_start) / frame_size;
570 quicktime_update_tables(out,
571 out->atracks[0].track,
572 audio_start,
573 out->atracks[0].current_chunk,
574 out->atracks[0].current_position,
575 samples,
577 out->atracks[0].current_position += samples;
578 out->atracks[0].current_chunk++;
581 audio_start = audio_end;
582 update_time = 1;
583 //printf("audio chunk %d\n", out->atracks[0].current_position);
586 if(update_time)
588 current_time = time(0);
589 if((int64_t)current_time - (int64_t)prev_time >= 1)
591 printf("samples %d frames %d\r", out->atracks[0].current_position, out->vtracks[0].current_position);
592 fflush(stdout);
593 prev_time = current_time;
602 printf("\n\n");
605 #endif
618 // Force header out
619 quicktime_close(out);
621 // Transfer header
622 FSEEK(in, 0x8, SEEK_SET);
624 data[0] = (ftell_byte & 0xff00000000000000LL) >> 56;
625 data[1] = (ftell_byte & 0xff000000000000LL) >> 48;
626 data[2] = (ftell_byte & 0xff0000000000LL) >> 40;
627 data[3] = (ftell_byte & 0xff00000000LL) >> 32;
628 data[4] = (ftell_byte & 0xff000000LL) >> 24;
629 data[5] = (ftell_byte & 0xff0000LL) >> 16;
630 data[6] = (ftell_byte & 0xff00LL) >> 8;
631 data[7] = ftell_byte & 0xff;
632 fwrite(data, 8, 1, in);
634 FSEEK(in, ftell_byte, SEEK_SET);
635 stat(TEMP_FILE, &ostat);
636 temp = fopen(TEMP_FILE, "rb");
637 FSEEK(temp, 0x10, SEEK_SET);
638 copy_buffer = calloc(1, ostat.st_size);
639 fread(copy_buffer, ostat.st_size, 1, temp);
640 fclose(temp);
641 fwrite(copy_buffer, ostat.st_size, 1, in);
643 fclose(in);