9 October 2011

Reading CD-Text with VB.Net (3)

Last time we had retrieved a big blob of data that holds the CD-Text. The mmc3 document tells us the blob is a series of CD-TEXT Pack Data, which we can call packs for short. All the information is contained in these packs, but the packs themselves describe a more complicated data structure that we need to recreate. I used the mmc document, patent application and open source projects to understand this structure.

All the text is stored in one Text Group, which is what we have retrieved. The text group contains up to 8 Blocks numbered 0 to 7. It is possible for a CD to have text in multiple languages, each Block contains text in one language only. Each Block is made up of Packs. A block can contain up to 255 Packs, each pack in a block will have a unique Sequence Number 0-255. If the text is particularly large then it may need multiple blocks to fit it all in. (One block contains text for one language only, one language may require multiple blocks.)

Each pack is 18 bytes in size and has three sections – a 4 byte header, 12 bytes of data and 2 bytes for a CRC. The header of a pack contains it’s block number. The header has one byte to hold the sequence number. The header also contains a number that identifies the type of data that the pack stores. For example the pack type &H80 means the data portion of the pack contains text data forming part of the album or track titles. As it’s only 12 bytes of data in a pack, you might need several packs to describe, say, the title of track 3. The track number is also stored in the header, track number 0 is special and means the data relates to the CD as a whole rather than a particular track. For the Title pack type, for example, track number 0 is the title of the album.

An important pack type is the size pack type. Each block has 3 size packs. These do not contain text data, instead the bytes map to various counts and other numerical information. The size packs describe a block’s:

  • character code - how the text data in the pack types that store text is encoded. E.g. ASCII
  • first track number, last track number
  • copy protection info
  • number of packs of each pack type in the block
  • last sequence number of each block (0 if the block number is not used, this information must be repeated in each block as it’s not specific to the block that contains the size packs)
  • language code – this tells us which language the block is for e.g. English, Afrikaans, French etc.

Most of the packs contain text information for the tracks or album. If we have an imaginary CD which has 3 tracks with title data for each track and the album, e.g:

  • Album title = “Hello World Album” (Track 0)
  • Track 1 = “I’m the urban spaceman”
  • Track 2 = “The intro and the outro”
  • Track 3 = “Poisoning pigeons in the park”

Then we would split each string up into chars and append a Chr(0) to indicate the end of that string. We chop it into 12 byte sections and make a pack for each section. This table shows the 18 bytes of a pack, 4 (H)eader bytes, 12 (D)ata bytes and 2 (C)rc bytes. /0 indicates a Chr(0). The first header byte is the pack type – for us &H80. The second header byte contains 7 bits identifying the track number (there’s another bit which we are going to say isn’t set). As the data might contain sections of information from multiple tracks, the track number identifies which track the first byte of track data belongs to. The block sequence number is in the third header byte – let’s imagine that these packs happened to start at block sequence number &H10. The eight bits of information in the fourth header byte encode three things – whether the text uses 1 byte or two per character, the block number (0 – 7) and the position that the first character in the data takes in it’s string. The character position isn’t useful to us (maybe it is useful in C or ASM). Let’s ignore this byte, and the Crc bytes, and just assume it’s all in one block.

H H H H D D D D D D D D D D D D C C
80 0 10 H e l l o   W o r l d  
80 0 11 A l b u m /0 I m   t h
80 1 12 e   u r b a n   s p a c
80 1 13 e m a n /0 T h e   i n t
80 2 14 r o   a n d   t h e   o
80 2 15 u t r o /0 P o i s o n i
80 3 16 n g   p i g e o n s   i
80 3 17 n   t h e   p a r k /0 /0

There are various pack types like this – for the title, performer, songwriter, composer, arranger, messages. There are also other pack types which contain other types of information, like the size pack type. For example, there are pack types for CD Information, Toc information and Genre information. These are always encoded in ASCII, and they are not encoded individually for each language on a multi-language CD.

I imagine that multi-language CDs don’t exist in the wild. When testing burning apps I found none of them would use multiple blocks either. It seems most CDs then will just have 1 block, 1 language and use ASCII, which makes it pretty easy to decode a CD. I’m not exactly sure what happens if you have to split a language over blocks. I guess it just picks up where the last one left off.

In .Net we can have some class CdText to store the properties that apply to the cd as a whole. Then it’s probably easiest to shove the language based information into a datatable. Here we have a primary key of (Language, Track No), and columns for things like Title, Performer, … etc. We can stick some helper properties onto the main class to list the available languages etc.