1 #include "funcprotos.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
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
45 #define FRAMERATE (double)30000/1001
47 #define SAMPLERATE 48000
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
68 #define GOT_IMAGE_START 6
69 #define GOT_IMAGE_END 7
72 #define NEW_TABLE(ptr, size, allocation) \
79 #define APPEND_TABLE(ptr, size, allocation, value) \
81 if((allocation) <= (size)) \
84 (allocation) = 1024; \
87 int64_t *new_table = calloc(1, sizeof(int64_t) * (allocation)); \
88 memcpy(new_table, (ptr), sizeof(int64_t) * (size)); \
92 (ptr)[(size)] = (value); \
98 int main(int argc
, char *argv
[])
103 int64_t current_byte
, ftell_byte
;
105 int64_t audio_start
= 0, audio_end
= 0;
106 unsigned char *search_buffer
= calloc(1, SEARCH_FRAGMENT
);
107 unsigned char *copy_buffer
= 0;
111 unsigned char data
[8];
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;
122 int state
= GOT_NOTHING
;
128 // Value taken from Cinelerra preferences
129 int audio_chunk
= 131072;
132 int64_t *start_table
;
134 int start_allocation
;
138 int64_t *field_table
;
140 int64_t field_allocation
;
144 printf("Recover JPEG and PCM audio in a corrupted movie.\n"
145 "Usage: recover [options] <input>\n"
147 " -b samples number of samples in an audio chunk (%d)\n"
153 for(i
= 1; i
< argc
; i
++)
155 if(!strcmp(argv
[i
], "-b"))
159 audio_chunk
= atol(argv
[i
+ 1]);
163 printf("Sample count for -b is out of range.\n");
169 printf("-b needs a sample count.\n");
180 // Dump codec settings
181 printf("Codec settings:\n"
182 " WIDTH=%d HEIGHT=%d\n"
200 in
= fopen(in_path
, "rb+");
201 out
= quicktime_open(TEMP_FILE
, 0, 1);
205 perror("open input");
214 quicktime_set_audio(out
,
219 quicktime_set_video(out
,
225 audio_start
= (int64_t)0x10;
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))
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
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)
292 APPEND_TABLE(field_table
, field_size
, field_allocation
, field
)
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
;
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)
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",
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",
348 current_byte
* (int64_t)100 / file_size
);
360 // With the image table complete,
361 // write chunk table from the gaps in the image table
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];
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
,
375 out
->atracks
[0].current_chunk
,
376 out
->atracks
[0].current_position
,
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
]);
401 quicktime_update_tables(out
,
402 out
->vtracks
[0].track
,
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
++;
426 if(!memcmp(VCODEC
, QUICKTIME_MJPA
, 4))
429 jpeg_header_offset
= 46;
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
++)
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
;
462 if(state
== GOT_FIELD1
)
464 field2_offset
= current_byte
+ i
- jpeg_header_offset
;
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;
483 if(state
== GOT_FIELD1
)
485 field2_offset
= current_byte
+ i
- 2;
490 if(search_buffer
[i
] == 0xff &&
491 search_buffer
[i
+ 1] == 0xd9)
495 // ffd9 sometimes occurs inside the mjpg tag
496 if(state
== IN_FIELD1
&&
497 current_byte
+ i
- field1_offset
> 0x2a)
503 if(state
== IN_FIELD2
&&
504 current_byte
+ i
- field2_offset
> 0x2a)
513 // BUZ driver puts padding in
514 for(j
= 2; j
< 8; j
++)
516 if(search_buffer
[i
+ j
] != 0xff)
519 if(search_buffer
[i
+ j
] == 0xd8 && got_ff
)
521 audio_start
= jpeg_end
= current_byte
+ i
+ j
- 1;
524 // Got end of video chunk
526 audio_start
= jpeg_end
= current_byte
+ i
+ j
;
537 if((fields
== 2 && state
== GOT_FIELD2
) ||
538 (fields
== 1 && state
== GOT_FIELD1
))
540 quicktime_update_tables(out
,
541 out
->vtracks
[0].track
,
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);
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
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
,
573 out
->atracks
[0].current_chunk
,
574 out
->atracks
[0].current_position
,
577 out
->atracks
[0].current_position
+= samples
;
578 out
->atracks
[0].current_chunk
++;
581 audio_start
= audio_end
;
583 //printf("audio chunk %d\n", out->atracks[0].current_position);
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
);
593 prev_time
= current_time
;
619 quicktime_close(out
);
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
);
641 fwrite(copy_buffer
, ostat
.st_size
, 1, in
);