This is a continuation of yesterday's post "
Fun with FAT32, and part 2 in the process of getting that video back.
OK, so we have two promising looking files that don't play. Let's dive into the AVI file format.
Microsoft have a published spec for it
here.
An AVI file is really a RIFF file. The basic structure of a RIFF file is a 4 byte identifier, followed by a 32-bit size. Then a length field, then a pad byte if the length isn't even. You then have a header with fields showing info like what codec etc etc. I'm not going to go into this in detail, because it's not actually important here (trust me). The one thing that is is that "DWORD", supposedly meaning "double word", a word being 32 bits, is not 64 bits. Microsoft compilers treat WORD and DWORD types as the same, or so I'm told. I'm just going to say "32 bit value" or "unsigned int", but even then, on some 64 bit platforms, ints are 64 bit, so be careful.
The code will be available in the SVN. Also excuse the hardcodedness of a lot of things in this program - AVI files from the same camera don't tend to differ. Fat32 volumes formatted in many different things do.
So I made a dodgy program to dump data from the headers and it would appear that our two mystery files are AVI files, but there's some stuff missing. The header stuff is in exactly the same position - my program just assumes this as I wasn't going to write a complete and proper RIFF implementation. So it likely won't work on AVI files not from this camera.
Playable one on the left, unplayable one on the right.
0x0, 0fps, 0 frames long. While the resolution is certainly known from the start, the number of frames quite reasonably isn't.
I attempted to drop reasonable values into the file, but to no avail - it still didn't play. In MPlayer, Quicktime or VLC. VLC offered to repair the AVI index, and promptly crashed upon doing so. Oh well.
The reason why previously I said that it didn't matter about the header is that I am just going to rip the data out of the AVI and deal with it elsewhere.
import sys
import block
import struct
import re
f = open(sys.argv[1],'r')
file = f.read()
Yes we do read the file in. What do you mean you don't have 500MB of RAM?
Now some calculations:
The RIFF chunk with the actual data in it is named "movi".
movicount = file.count("movi")
print "movi block count: %i" % movicount
moviloc = file.find("movi")
print "movi block location: %i" % moviloc
There should only be one instance of "movi" in the file. It's an outstanding coincidence if this happens in the video data. Or it's annoying if it happens in the header. We'll just hope it doesn't.
print "regexing..."
avidata = file[moviloc+4:]
avichunks = re.split("(00dc|01wb)",avidata)[1:]
chunktype = avichunks[0::2]
chunkdata = avichunks[1::2]
First, we get the entire file from 4 bytes after the "movi" chunk. We ignore it's size field - remember that this is a not properly finished file we are dealing with, and fields such as these may not be present yet.
Now for some regex. We need to use re.split because the string split method doesn't support splitting on multiple things, and loses what it was split by.
The way AVI works is that it interleaves audio and video data. This means that reading and writing can be done in one stream, greatly simplifying the recording and playback process. There are no seperate reads or seek operations to two seperate parts of the file (in the days AVI was new, we played fancy multimedia CD-ROMs. Seeking on CD-ROMs is horribly slow, but linear reads weren't so bad.). We have RIFF chunks with types indicating what they are.
The first two digits identify the track number. dc means compressed video, and wb means audio data. There are four such codes - read the earlier linked spec to find out about them.
You can have an overcomplicated regex here like "(\d\d)(dc|wb)(.*)" - I did originally, but it was actually matching movie data. There were no false matches with this one, and no tracks we are interested in besides the video and audio tracks.
Anyway, this results in a list like so:
["","00dc","block of video data","00dc","more video data","01wb","audio data"]
and so on.
Next, if you don't know, in Python, array indexes can have up to 3 values.
a[1] = item 1 of a (second item, arrays are indexed from 0)
a[:] = all of A (same as "a")
a[1:] = from item 1 onwards
a[1:3] = from 1 to 3
a[-1] = last value
The third number is "every n".
a[0::2] is every second value starting at 0, or every odd value.
a[1::2] is every even value.
a[::-1] is the array in reverse order.
Anyway, we drop the first value of the array because it's an empty string.
chunktype is the first element of every pair, chunkdata is the second.
Now let's do something with them:
streams = {}
streams["00dc"] = []
streams["01wb"] = []
for i in xrange(len(chunktype)):
stype = chunktype[i]
sdata = chunkdata[i]
streams[stype] += [sdata]
We initialize the dictionary "streams" with two lists. Then we go through all the chunks, and place them at the end of the list they belong to.
Now let's do something with this dictionary.
for stream in streams.keys():
f = open("streams/%s" % stream,'w')
print "writing streams/%s" % stream
for block in streams[stream]:
f.write(block)
f.close()
Right, let's do this.

Promising.
Now we know that the video is motion JPEG, and that the audio is uncompressed. Let's look at one of the known good movies:

I'll leave the video for later. Let's deal with the audio first. I'll use Audacity for this.

And what do we have?

It sounds like the radian... but what is that fast clicking in the background?
There seems to be some extra bytes in here. Ahh... we forgot about the size bytes after all the audio blocks!
Change the line where the blocks are written to this, and run again.
f.write(block[4:])
And no clicking. I can hear the Radian's motor running flat out and producing some very distorted sound, followed by it shutting off, and silence. Then it being run shortly... shouldn't there be some wind in here? I skip to the end, expecting the recording to end with a loud bang... it ends with me saying something after the motor winding down. This isn't the video - but why isn't it in the filesystem? I remember pulling the power too soon on a test run of my camera. That must be it. It must be the unidentified 18MB file, not the 70MB one. Or it could be neither of them. Hmm.
Let's run it on the other file.
It only produces 28 seconds of sound.

The first few spikes are the canopy being put on, followed by the motor starting at 9 seconds in, and the throttle being maxed out at 18 seconds. The video ends 8 seconds later, still at full throttle. This doesn't sound like a test. I'm not saying anything. But I can't hear wind noise in it. And I could have sworn it took longer than 18 seconds from video start to crash the plane. I check another video - and it seems that at full throttle, there isn't wind noise - it's not audible above the motor.
I guess there's one way to find out - let's mess with the video. VLC won't touch the 00dc file. MPlayer doesn't like it. Quicktime... wait what? A video!
No. A frame. Damnit.

But it's the video. I'm turning the camera on from the transmitter. I'm standing where I was standing when I launched the plane for it's disasterous flight. This is the video.
Now let's take a look at the data.

FF D8? That sounds oddly familiar. A quick Google search for "jpeg magic bytes" finds that it is the marker for the start of a JPEG file. FF D9 is supposed to be the end. I search for it. Not found. But FF D8 is in this file... a lot.
A search for Motion JPEG format finds
this page from the Planning for the Library of Congress Collections. It doesn't say much about the video format itself, but it does say:
Avery Lee, writing in the rec.video.desktop newsgroup in 2001, commented that "MJPEG, or at least the MJPEG in AVIs having the MJPG fourcc, is restricted JPEG with a fixed -- and *omitted* -- Huffman table. The JPEG must be YCbCr colorspace, it must be 4:2:2, and it must use basic Huffman encoding, not arithmetic or progressive. . . . You can indeed extract the MJPEG frames and decode them with a regular JPEG decoder, but you have to prepend the DHT segment to them, or else the decoder won't have any idea how to decompress the data. The exact table necessary is given in the OpenDML spec."
Interesting.
I take a rough guess that I should split all the JPEGs out at FF D8.
#!/usr/bin/env python
import sys
import block
import struct
import re
f = open(sys.argv[1],'r')
file = f.read()
file = file.split("\xFF\xD8") # JPEG magic bytes
index = 0
for frame in file:
 f = open("frames/%04i.jpg" % index, 'w')
 f.write("\xFF\xD8" + frame)
 f.close()
 index += 1
Not too complicated.

Hmm, they seem to have icons. It's obvious why. Finder must be using the standard Apple libraries which are probably descended off Quicktime. Which when Quicktime notices that the JPEG has no DHT segment, uses the one from it's Motion JPEG decoder. Brilliant. Except that all these pictures can be displayed in Safari. Wait, that's an Apple app. Chromium? Still works. Imagemagick even displays it!
I look at
the Wikipedia article for JPEG. FF C4 is the marker for a Huffman table. And there's one there. I know what's going on here. The camera's chipset is feeding it JPEGs, and they're just bit-stuffing them into an AVI, at a rate of 30 of them every second. I guess none of the MJPEG decoders are picky.
So, after deleting 0000.jpg (it's the bit of data from the start), I import the image sequence into QuickTime Pro at 30fps. It looks a little bit fast. I'm not sure why at this point, but I combine the audio and it doesn't match up. So I bring both into Final Cut Pro. I note the duration of the audio track, and slow the video track down to match the length. The video matches the audio perfectly. And I see for the first time, the video. However, it runs out of frames in the air, pointed upwards.

Recovered crash video attempt 1 from gm on Vimeo.
Did the camera not finish writing to the card? I remember from the firmware upgrade that it has 8MB of DRAM in it. It's probably not storing anywhere near 8MB of data in the RAM, most likely a few JPEG frames until it has enough to write a block into the AVI. Besides, 8MB would only be a few seconds. It felt like longer from that point to where the plane came down.
I remember though, from the first step.
Traversing files...
Error: nextptr 00004000 is unallocated
Error: nextptr 0005E000 is unallocated
I wonder if it's writing the FAT one sector at a time. This would mean that the sectors are not actually marked as used, but that there is data there - after all it had to go somewhere. This was the last video of the day, and the card was not mounted prior to imaging, so if there is - it hasn't been overwritten. And since it's FAT, the allocation should be reasonably linear. Time to go back to stage 1 I guess. It will be obvious if the zeros are on a 512 byte boundary.
Convert endianness, and 00E00500 is present in the FAT.

It is. The next line is 0x00017CC00 = 1559552. 1559552 / 512 = 3046 exactly. That's a block boundary alright.
(You are a hardcore geek if you know 512 = 0x200 and recognised it was a multiple from looking at the hex offset value).
0x0005E000 = 385024.
Remembering from the program, the address of a cluster is:
firstDataSector = (numReservedSectors * bytesPerSector) + (numFATs * fat1length)
address = (((cluster - 2) * sectorsPerCluster) * bytesPerSector) + firstDataSector
So this gives us:
((385024 - 2) * 8 * 512) + (38 * 512) + (2 * 7771648)
1577050112 + 19456 + 15543296
= 1592612864
= 0x5EED5800

There's more data here, and this data is identical to the end of 33.avi.
Not much further down, there's a 00dc (AVI image data block), 4 size bytes, and a FF D8 (JPEG start of image marker if you've forgotten)!

The first lot of all zeros is at 0x0614B3600 = 1632318976
1632318976 (what we thought was the end) - 1592612864 (where the 0 blocks start) = 39706112 (of extra data).
39706112 / 1048576 = 37MB more, hopefully of the same file!
Now that we know where it is, we can dig it up, and hope it's in order, because there's no FAT linked list to tell us that it is.
Anyway, I'll continue this later, and hopefully find some results.