I've just started a little C programming project in order to improve my C programming and, indeed, to see if I'm actually clever enough to write something in C. The project I've chosen is a little ID3 tag editor for MP3 files.
I've chosen this because it might be useful and because it involves something I quite enjoy, which is parsing files. It's also the sort of project that doesn't need a GUI but can later be extended to have one.
I chose C because, as stated earlier, I wanted to prove to myself that I can do it and because it's a great language for byte level manipulation of the sort required to parse a binary file like an MP3.
MP3 files support two types of ID3 tag, these being version 1 and version 2 (of which there are 4 types). ID3v1 tags reside at the end of the mp3 file and are limited in length to 128 total bytes: 3 for the keyword "TAG", 30 each for the title, artist and album, 4 for the year, and then either 28 or 30 for a comment, and, if using a 28 byte comment, 2 for the track number, and finally 1 byte for the genre.
ID3v1 tags are therefore trivial to manipulate.
ID3v2 moved the tags to the start of the file and introduced the concept of separate frames of data, each to store one tag and its value.
The ID3v2 header starts with the values "ID3" (0x49 0x44 0x43 in hex) followed by the minor version number (0x03 for ID3v2.3, 0x04 for ID3v2.4, etc) and then the build number (usually 0), a byte for flags related to the ID3 tagss, and finally four bytes for the overall ID3 header size. Thus an ID3v2 header can be identified by looking like this:
49 44 33 vv vv ff xx xx xx xx
Where vv are the version bytes, ff is the flags, and xx xx xx xx is the big-endian header size.
When I first started writing the decoding function for the ID3 header I was reading the number of bytes as dictated by the xx xx xx xx in the header, but found I was reading well beyond the end of the header itself, causing all sorts of problems. This led me to a google search where I discovered the concept of "Sync-Safe Integers".
These sync-safe integers are not a universal computing concept, but rather one created purely for MP3 encoding, it seems. And this is where sychronisation safe integers come in.
In order for those four bytes indicating header size to not be confused for later bytes that are used to synchronise data frames they cannot contain the values "FF 00" and the way to do this is to ensure that no bytes not intended for syncronisation contain the value FF unless always guaranteed to be followed by a non-zero value, such as the byte order marker FF FE.
To do this, we insert a zero at bit 8 (the left-most bit, and most-significant bit) of each byte, working from least to most significant, and shift the rest of the bytes accordingly:
Thus the value:
00000001 00000110 00010100 11000001
becomes
00001000 00011000 00101000 01000001
This has the side-effect of reducing the capacity of our integer from being 32 bits to being 28 bits, but in the context of ID3 tags that limitation is irrelevant.
For my own program, I achieved this shift by using a bit mask to take each group of bits I want to operate on in turn, applying the mask as a bitwise AND to the integer, and then shifting the requisite number of positions to the right.
I then bitwise ORed the results together:
return ((in & 0x0000007F) | // bit mask %00000000 00000000 00000000 01111111
((in & 0x00003F80) << 1) | // bit mask %00000000 00000000 00111111 10000000
((in & 0x001FC000) << 2) | // bit mask %00000000 00011111 11000000 00000000
((in & 0x0FE00000) << 3)); // bit mask %00001111 11100000 00000000 00000000
Producing a sync-safe integer from a regular integer.
The decoding sequence is just the reverse:
return ((in & 0x0000007F) | // bit mask %00000000 00000000 00000000 01111111
((in & 0x00007F00) >> 1) | // bit mask %00000000 00000000 01111111 00000000
((in & 0x007F0000) >> 2) | // bit mask %00000000 01111111 00000000 00000000
((in & 0x7F000000) >> 3)); // bit mask %01111111 00000000 00000000 00000000
In both cases, the ORed instructions are independent of each other in the sense that each has its bitmask applied and is shifted BEFORE all four results are ORed together.
And using this approach I was able to now correctly identify the length of the ID3 tags. Note, this value includes any padding at the end of the tags and before the audio data.
So that's sync safe integers - something you'll never encounter outside the realms of writing a decoder for ID3 tags.