Click here to Skip to main content
15,888,216 members
Articles / Mobile Apps / Windows Mobile
Article

Tutorial on reading Audio-CDs

Rate me:
Please Sign up or sign in to vote.
4.79/5 (34 votes)
9 Oct 2006CPOL7 min read 153.8K   1.9K   98   31
This easy-to-get tutorial explains in detail everything you need to know about audio-CDs and how to rip the tracks.

Introduction

I really got mad trying to find tutorials about audio-CDs. I found a few with some documentation about the structure of an audio-CD, but nothing that I could use for my "Save-Down-Audio-Tracks-To-File"-class. So, as you see, at last I got it... On my way up to my code I basically relied on two articles. One of them, written by Idael Cardoso and published here at CodeProject, is unfortunately written in C# and the technique of audio-CD-ripping isn't explained at all, as well as the code is not commented that well... The other article, in fact a series of articles by Larry Osterman, didn't make it that easy to rebuild code. So I wrote my class with the basic functionality (and some of it stolen from Idael and Larry). Regarding the fact that I did not find any reliable C++ source about ripping audio-CDs for hours of searching, I decided to publish my class with some explaining around.

Audio Formats

In the whole article I talk about uncompressed wave-audio in the PCM-Format. So don't ask me anything about MP3s or the like. Wave-data has some attributes. The attributes which decide about a wave-data's appearance are:

  • Bits per sample:The bits per sample determine the accuracy and bandwidth of frequencies which a sound contains. Usual values are 8 bit and 16 bit. As you may suppose, a single 16 bit-frequency needs a 16-bit-word-variable to be contained.
  • Count of Channels: Specifies the count of channels the wave-data uses. Usual values are 1 (mono) and 2 (stereo). Wave-data in stereo needs twice the size as mono-data. The frequencies in stereo-waves are stored in blocks like [Frequ Left Channel] [Frequ Right Channel] [Frequ Left Channel] ...
  • Sampling-Rate: A single block contains the frequency-data of all channels for one single moment. So a block of a wave-sample using stereo 16 bit, would be 4 bytes of size (ChannelCount*(BitsPerSample/8)). The sampling-rate determines how many blocks are used to display 1 second. Usual values within waves are 44100Hz, 22050Hz, 11025Hz and 8000Hz.

With these three attributes given (bits/second, channels, sampling-rate) you are able to compute the needed size for wave-audio. As an example, the audio-CD format always is 44100Hz, 16 bit, stereo. For one second of audio 44100*2*2 bytes are required. If you need 176400 byte for one second, you'll need 846720000 bytes (807MB) for 80 minutes, which is the maximum size of data fitting on an audio-CD.

So it's no wonder the MP3-format became such popular! An audio-sample with CD-quality which is 4 minutes long needs about 40 MB on your hard disk. The MP3-file takes about 4 MB with almost equivalent quality!
BTW: The expression "frequeny" is not the real meaning of the audio-data. It represents something close to it, but I think it's OK to imagine it in that way.

About audio-CDs

The data stored on CDs is determined in sectors. A "normal" CD-sector takes 2048 bytes (2KB) of size. Something special about audio-CDs is, that their audio-data is stored in sectors of 2352 bytes of size. That is because one sector should store 1/75 of one second of audio-data. One second needs 176400 bytes, so 1/75 needs 2352 bytes.

Each audio-CD contains a table of contents (TOC). It holds information about the track-count and the address of every track on CD. Usually Windows loads the TOC when you insert the CD and is updated on CD-change. You will retrieve the TOC by a single call to the CD-ROM drive. Because Windows holds the TOC, the CD-drive is not spinned up to get the data, you'll get it directly from your OS. Here you see it's structure:

C++
typedef struct _TRACK_DATA
{
    UCHAR Reserved;
    UCHAR Control : 4;
    UCHAR Adr : 4;
    UCHAR TrackNumber;
    UCHAR Reserved1;
    UCHAR Address[4];
} TRACK_DATA;

typedef struct _CDROM_TOC
{
    UCHAR Length[2];
    UCHAR FirstTrack;
    UCHAR LastTrack;
    TRACK_DATA TrackData[100];
} CDROM_TOC;

The CDROM_TOC-structure contains the FirstTrack (1) and the LastTrack (max. track nr). CDROM_TOC::TrackData[0] contains info of the first track on the CD.
Each track has an address. It represents the track's play-time using individual members for the hour, minute, second and frame. The "frame"-value (Address[3]) is given in 1/75-parts of a second -> Remember: 75 frames form one second and one frame occupies one sector.
To specify the size in sectors of the wave-track, use the following function:

C++
ULONG AddressToSectors( UCHAR Addr[4] )
{
    ULONG Sectors = Addr[1]*75*60 + Addr[2]*75 + Addr[3];
    return Sectors - 150;
}

As you may have noticed, the hours-value of the address is not used. I can't see any sense in it, but if a CD-track exceeds 60 minutes, the hours-value stays unused and the minutes exceed the 60-mark. A value of 150 is subtracted because, as I said, the first accessible address is 2 seconds (150 frames) behind the CD-start.

To read out the track-data we need to have the address and the length of a track, both in sectors.

For my class I chose quite a tiny structure to hold the track-info.

C++
struct CDTRACK
{
    ULONG Address;
    ULONG Length;
};

To calculate the address, just pass the TRACK_DATA::Address-value to AddressToSectors. To calculate the length, subtract the sector's address from the next track's sector-address.

C++
CDROM_TOC Toc;
CDTRACK SmallData;

SmallData.Address = AddressToSectors( Toc.TrackData[x].Address );
SmallData.Length = AddressToSectors( Toc.TrackData[x+1].Address )
                   - SmallData.Address;

Accessing the disc-drive

Once you know, the access to the disc-drive is really simple. You create a handle using CreateFile, communicate with the CD-drive using DeviceIoControl and close that handle via CloseHandle.

Using "CreateFile"

Many of you will know the usage of CreateFile, so here's just a short line of code on how to create the handle.

C++
char Fn[8] = { '\\', '\\', '.', '\\', Drive, ':', '\0' };
HANDLE hCD = CreateFile( Fn, GENERIC_READ, FILE_SHARE_READ,
                         NULL, OPEN_EXISTING, 0, NULL );

Note the path-parameter for CreateFile. It must have the form \\.\F: (in case F is your CD-drive).

Using "DeviceIoControl"

Be aware: DeviceIoControl works well with Win2000/XP. To use it with Win95/98/Me click here.

C++
BOOL DeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped);

hDevice takes our handle to the CD. dwIoControlCode gets one of the IOCTL_... messages and the next parameters specify the input, output and their size. Additionally, there's a dummy-parameter (lpBytesReturned) to which we will always pass some ULONG. We won't use the lpOverlapped-param, so set it to NULL.
There are several IOCTL-messages we are interested in:

  • IOCTL_CDROM_READ_TOC: Reads out the TOC as described above. Set both input-parameters to 0, output-parameters to CDROM_TOC* and sizeof(CDROM_TOC)
  • IOCTL_CDROM_RAW_READ: Reads raw data from the CD-drive. You have to pass a RAW_READ_INFO* and sizeof(RAW_READ_INFO) as input and a valid buffer-pointer with the bytes it can contain as output.
    At this point my code failed for a long time. In RAW_READ_INFO you specify from which sectors and how many sectors you want to read. Just specifying the song's sector-data (e.g. address=4492, length=16110 sectors) in RAW_READ_INFO, the call to DeviceIoControl will fail with GetLastError set to 87, ERROR_INVALID_PARAMETER.
    There is a maximum of sectors to be read at once! I did not find the maximum-number and perhaps it's drive-dependant. But be sure a value <= 1000 should work and a value around 20 is really safe.

Here an example, how to use IOCTL_CDROM_RAW_READ. Regarding the fact that we are not allowed to read a wave-track at once, we need to read it out piece for piece. It's an excerpt from the code:

C++
CDTRACK Track; // Filled with valid info
char* pBuf = new char [Track.Length*2352];

RAW_READ_INFO ReadInfo;
ReadInfo.TrackMode = CDDA; // Always use CDDA (numerical: 2)
ReadInfo.SectorCount = 20; // We'll read 20 sectors with each operation.

// Read the track-data in a loop. Read 20*2352 bytes per pass.
for ( ULONG i=0; i<Track.Length/20; i++ )
{
    // Calculate the new offset from where to read.
    ReadInfo.DiskOffset.QuadPart = (Track.Address + i*20) * 2048;

    // Call DeviceIoControl and read the audio-data to out buffer.
    ULONG Dummy;
    if ( 0 == DeviceIoControl( hCD, IOCTL_CDROM_RAW_READ,
                               &ReadInfo, sizeof(ReadInfo),
                               pBuf+i*20*2352,
                               20*2352,
                               &Dummy, NULL ) )
    {
        delete [] pBuf;
        return FALSE;
    }
}

// Read the remaining sectors.
ReadInfo.SectorCount = Track.Length % 20;
ReadInfo.DiskOffset.QuadPart = (Track.Address + i*20) * 2048;
ULONG Dummy;
if ( 0 == DeviceIoControl( hCD, IOCTL_CDROM_RAW_READ,
                           &ReadInfo, sizeof(ReadInfo),
                           pBuf+i*20*2352,
                           ReadInfo.SectorCount*2352,
                           &Dummy, NULL ) )
{
    delete [] pBuf;
    return FALSE;
}

delete [] pBuf;

Looks quite simple, huh? The only thing I tumble over is the number 2048. This is the only thing about the whole CD-ripping that I really do not understand! It would make sense if you'd replace the number 2048 with 2352! It's pretty weird... But that's the only way it works and was a huge hurdle (if you do not have great tutorials).

The rest of the code should be self-explanatory. At first, the data is read in a loop, 20 sectors (20*2352 bytes) per pass. Then the remaining sectors are read. During the read-process, the correct cd-offset and buf-offset are calculated and the audio-data is stored to that computed buffer-offset.

Additional, there are some more IOCTL_...-messages of interest:

  • IOCTL_STORAGE_CHECK_VERIFY: Checks whether your CD-drive is accessible
  • IOCTL_STORAGE_LOAD_MEDIA: Injects the CD-drive if opened
  • IOCTL_CDROM_EJECT_MEDIA: Ejects the CD-drive
  • IOCTL_CDROM_GET_CONFIGURATION: Retrieves the type of disk (CD-ROM/CD-R/CD-RW/DVD-ROM/DVD-R/...)
  • IOCTL_CDROM_PLAY_AUDIO_MSF, IOCTL_CDROM_PAUSE_AUDIO, IOCTL_CDROM_RESUME_AUDIO, IOCTL_CDROM_STOP_AUDIO: Plays audio data. Pretty simple to control!

Using the code

The class CAudioCD was written to extract audio-tracks from a CD onto your hard-disc. That's the reason why the class is not able to do much more than that. It is able to:

  • Get some info about the count of tracks and each track's play length
  • Read tracks into memory
  • Read tracks from CD directly to a wave-file
  • Do the reading in an extra-thread
  • Inform you about the current progress (callback)
  • Inject & eject the CD, my favourite :D

Using the code should be really simple. The main class is CAudioCD. Here's an example on how to use the class:

C++
#include "CAudioCD.h"
#include <stdio.h>

#define MY_CDROM_DRIVE 'F'

void OnAudioCDProgress( ULONG Track, ULONG Percentage, VOID* Param )
{
    printf( "Ripping track nr. %i\n", Track );
    printf( ": Progress at %i%%\n", Percentage );
}

int main( ... )
{
    CAudioCD AudioCD;
    if ( ! AudioCD.Open( MY_CDROM_DRIVE ) )
    {
        printf( "Cannot open cd-drive!\n" );
        return 0;
    }

    ULONG TrackCount = AudioCD.GetTrackCount();
    printf( "Track-Count: %i\n", TrackCount );

    for ( ULONG i=0; i<TrackCount; i++ )
    {
        ULONG Time = AudioCD.GetTrackTime( i );
        printf( "Track %i: %i:%.2i;  %i bytes of size\n", i+1,
                Time/60, Time%60, AudioCD.GetTrackSize(i) );
    }

    // Prepare param for reading...
    AUDIOCD_READTRACK ReadInfo;
    ReadInfo.Track = 7;
    ReadInfo.SaveToFile = "C:\\Song.wav" );
    ReadInfo.ProgressCb = OnAudioCDProgress;

    if ( ! AudioCD.ReadTrack( &ReadInfo ) )
        printf( "Cannot start reading track: %i\n", GetLastError() );

    return 0;
}

I won't explain anything about this code, read it and you will understand.

Finally...

I hope the article was interesting for you and I hope you'll forgive my bad English, I'm a native German and did write my last English essay in school 2 years ago. So, contact me for any grammatical or spelling mistakes or if I wrote something totally wrong about some audio-stuff.

Greetings, Michel

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
MichelHelms@Web.de

Comments and Discussions

 
SuggestionThe reason why RAW_READ_INFO.DiskOffset is always *2048 and not *2352... Pin
Member 1200606314-Oct-15 23:30
Member 1200606314-Oct-15 23:30 
The reason why RAW_READ_INFO.DiskOffset is *2048 and not *2352 is simply because the Microsoft IOCTL API for is implemented that way. It is clearly described in the Windows API documentation in threir IOCTL_CDROM_RAW_READ control code -> RAW_READ_INFO structure (http://msdn.microsoft.com/en-us/library/windows/hardware/ff563958.aspx) which reads:

DiskOffset
Contains an offset into the CD-ROM disc where data will be read.
You can calculate this offset by multiplying the starting sector
number for the request times 2048.

That's it. It may be not very intuitive for people who are just concentrating on reading in the CDDA mode, but mind you there are also other IOCTL_CDROM_RAW_READ.TrackMode's defined in the API (namely RawWithC2AndSubCode, RawWithC2, RawWithSubCode) which would have even bigger sector block size than 2353 (2744 bytes, 2648, 2448 respectively). You don't really want to calculate .DiskOffset differently each time, depending on RAW_READ_INFO.TrackMode selected so it is comprehensible that the API for IOCTL_CDROM_RAW_READ is made that way (always *2048).
QuestionHow to fast reverse the song or any media in c#.net. Pin
Akash khurmi2-May-12 0:55
Akash khurmi2-May-12 0:55 
GeneralPossible cause of maximum read size Pin
Pascal Damman27-Mar-08 5:41
Pascal Damman27-Mar-08 5:41 
GeneralSome questions Pin
chocm18-Jan-08 0:09
chocm18-Jan-08 0:09 
GeneralCD graphics Pin
SreeniTheGinie11-Nov-07 17:40
SreeniTheGinie11-Nov-07 17:40 
GeneralGreat stuff Pin
Paul Sanders (the other one)26-Oct-07 10:44
Paul Sanders (the other one)26-Oct-07 10:44 
Generalcompiling the code Pin
minostro16-Sep-07 20:04
minostro16-Sep-07 20:04 
GeneralRe: compiling the code Pin
Michel Helms17-Sep-07 7:23
Michel Helms17-Sep-07 7:23 
GeneralRe: compiling the code Pin
minostro18-Sep-07 3:57
minostro18-Sep-07 3:57 
GeneralRe: compiling the code Pin
Michel Helms21-Sep-07 12:30
Michel Helms21-Sep-07 12:30 
GeneralRe: compiling the code Pin
DNeptune17-Dec-07 10:52
DNeptune17-Dec-07 10:52 
GeneralRe: compiling the code Pin
Michel Helms19-Dec-07 1:42
Michel Helms19-Dec-07 1:42 
GeneralRe: compiling the code Pin
DNeptune21-Dec-07 10:42
DNeptune21-Dec-07 10:42 
Questioncan u guide me i am new for mixer control Pin
rajneshmalik30-Aug-07 2:16
rajneshmalik30-Aug-07 2:16 
AnswerRe: can u guide me i am new for mixer control Pin
Michel Helms31-Aug-07 13:15
Michel Helms31-Aug-07 13:15 
QuestionWhere's the "good" audio data? Pin
dzeta200628-May-07 5:27
dzeta200628-May-07 5:27 
AnswerRe: Where's the "good" audio data? Pin
Michel Helms29-May-07 1:57
Michel Helms29-May-07 1:57 
AnswerRe: Where's the "good" audio data? Pin
Paul Sanders (the other one)26-Oct-07 11:30
Paul Sanders (the other one)26-Oct-07 11:30 
GeneralFinally Got It Pin
jstclairfindlay3-Apr-07 1:47
jstclairfindlay3-Apr-07 1:47 
GeneralVery nice Pin
jerryf6512-Mar-07 14:11
jerryf6512-Mar-07 14:11 
GeneralRe: Very nice Pin
Michel Helms12-Mar-07 23:17
Michel Helms12-Mar-07 23:17 
GeneralCD-Text Pin
Gilles Montreal Canada6-Oct-06 7:38
Gilles Montreal Canada6-Oct-06 7:38 
GeneralRe: CD-Text Pin
Michel Helms7-Oct-06 2:04
Michel Helms7-Oct-06 2:04 
AnswerRe: CD-Text Pin
Gilles Montreal Canada7-Oct-06 11:22
Gilles Montreal Canada7-Oct-06 11:22 
GeneralRe: CD-Text [modified] Pin
Michel Helms7-Oct-06 12:33
Michel Helms7-Oct-06 12:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.