Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C Sharp Ripper

0.00/5 (No votes)
13 Jan 2004 4  
C# code to handle CDROM drives and read CD tracks

Demo program

Introduction

This code shows a way to make a CD ripper in C#. There are APIs from some vendors that allow reading audio CD tracks but it is also possible to do it using APIs that allow low level access to CD drives such as ASPI from Adaptec or IOCTL control codes. The latter method is used in this case, because there is no need to install any third party software, it is completely covered by Win32 API functions (CreateFile, CloseHandle, DeviceIoControl, GetDriveType).

Interop is used to call the mentioned Win32 functions to open the CD drive and send IOCTL codes to open, close the drive, read state and make a raw read of audio tracks. It is true that the faster and more "logical" way of to do the same thing would be using C++.NET but using C# and Interop is a valid way and not necessarily less efficient, of course, it needs an API translation.

In this work there is code from the article A low level audio player in C# by Ianier Munoz

Background

In order to read any audio track of a CD the first thing to do is to read the TOC (Table of Contents) from the CD. The TOC is a structure that contains the information like: the first and last track of the CD, and a fixed length list of structures that describe the information of each track (first sector where track starts, type of track, etc.) Those structures are defined in ntddcdrm.h with the names CDROM_TOC and TRACK_DATA respectively. I think it is interesting to describe the translation of mentioned structures to C# because it is needed to make some tricks in order to define these structures in C# to pass them as parameters using Platform Invoke: see Some translation details at the end of the article.

Using the code

The main code is a class library named ripper. The class CDDrive is the main class in all the process and includes the logic for all CD operations (get access to the drive, notify about drive changes, obtain CD information and drive status, obtain CDROM drive letters and, of course, read tracks). Here is a simple code that shows how we can use this class to read a particular track:

CDDrive Drive;
Drive = new CDDrive();
if ( Drive.Open(CDDrive.GetCDDriveLetters()[0]) ) //Get the first CD drive 

in the system.
{// We have access to CD drive

  if ( Drive.IsCDReady() )
  { //There is a CD in the drive

    if ( Drive.Refresh() )
    { //TOC have been read

      int Tracks = Drive.GetNumTracks();
      byte[] Buffer = new byte[4096];
      Drive.LockCD();
      try
      {
        for (int i = 1; i <= Tracks; i++)
        {
          Stream Strm = new FileStream(string.Format("track{0:00}.raw", i), 
                                       FileMode.Create, FileAccess.Write);
          try
          {
            uint Size = 0;
            while ( Drive.ReadTrack(i, Buffer, ref Size, null) > 0 ) 
            //If there is no error and datawas read 

            //successfully a positive number is returned

            {
             Strm.Write(Buffer, 0, (int)Size);
            }
          }
          finally
          {
            Strm.Close();
          }
        }
      }
      finally
      {
        Drive.UnlockCD();
      }
    }
  }
}

The above code saves all tracks of a CD inserted in the first CD drive in files named trackXX.raw in raw format. This is one of the simplest ways of using the mentioned class to read track data of a CD. I think that looking at the code it can be understood without further explanation (if I'm wrong then let me know it). In the demo project (a simple ripper), it is used in a more complex and complete way to read the track: it is used as a delegate to notify the progress (the preceding code uses null as the last parameter or ReadTrack to avoid progress notification) and a delegate that saves the data read in WAV file. It is also used with more functions of the class like insertion notification, open/close CD drive door, etc. The complexity in using those functions is not more than calling some methods or assigning a handler to events defined in the class.

While this code handles the CD insertion/removing notification, the AutoRun is not suppressed programmatically so it is advisable to disable AutoRun in your CD before using the code. According to Microsoft, to disable programmatically the AutoRun feature, one must handle the Windows Message QueryCancelAutoPlay, but this message is only sent to the currently active window and, at least in Windows 2000, for data CD containing the AUTORUN.INI file; for audio CD the default CD player is launched. In Windows XP, there are APIs for gracefully handling all media insertions and AutoRun but these are only available on Windows XP.

The UI of the demo program is not very intuitive and less pretty :-) but was made just to show how to use the code. Even if the code and demo works it is important to note that it was not made as a project that can be used in a final solution. There is no complete error detection and handling, within others. Another important thing that is not included is an algorithm of error correction; it is known that when digitally reading a CD, there could be some errors due to head alignment and others, that�s why most rippers include an error correction algorithm. Even without error correction algorithm it is possible to obtain acceptable results with many drives (old ones behave poorly) and CDs, also some drives include error correction by hardware so the information digitally read is reliable.

Some translation details:

Here is the C definition of TRACK_DATA and CDROM_TOC structures:

#define MAXIMUM_NUMBER_TRACKS 100

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[MAXIMUM_NUMBER_TRACKS];
} CDROM_TOC;

The translation of the TRACK_DATA structure is easy; the only thing to take into account is that, in C# there is no possibility to indicate bit size of a field in a structure. To obtain the same behavior, we can define a private field that holds all bit sized fields and use public properties to represent bit sized fields (as done in the present work). The problem here is the translation of CDROM_TOC, the more logical translation would be:

[StructLayout( LayoutKind.Sequential )]
public class CDROM_TOC
{
  public ushort Length;
  public byte FirstTrack = 0;
  public byte LastTrack = 0;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst=MAXIMUM_NUMBER_TRACKS)]
  public TRACK_DATA[] TrackData;

  public CDROM_TOC()
  {
    TrackData = new TRACK_DATA[MAXIMUM_NUMBER_TRACKS];
    Length = (ushort)Marshal.SizeOf(this);
  }
}

Of course there is the more logical translation but you would get a runtime error in the constructor because MarshalAs can't determine the size of the structure. The problem is that SizeConst could only be specified for fundamental types, it doesn't work for an array of structures (a lack of Interop). This could be solved using a custom marshalling, but custom marshalling is only for function parameters not for fields structures (another lack of Interop). Waiting for Microsoft to fix those details of Interop or any other better solution. Here's a solution:

[ StructLayout( LayoutKind.Sequential )]
public class TrackDataList
{
  [MarshalAs(UnmanagedType.ByValArray, SizeConst=MAXIMUM_NUMBER_TRACKS*8)]
  private byte[] Data;
  public TRACK_DATA this [int Index]
  {
    get
    { /* Code in the source files */
    }
  }
  public TrackDataList()
  {
    Data = new byte[MAXIMUM_NUMBER_TRACKS*Marshal.SizeOf(typeof(TRACK_DATA))];
  }
}

[StructLayout( LayoutKind.Sequential )]
public class CDROM_TOC
{
  public ushort Length;
  public byte FirstTrack = 0;
  public byte LastTrack = 0;
  public TrackDataList TrackData;
  public CDROM_TOC()
  {
    TrackData = new TrackDataList();
    Length = (ushort)Marshal.SizeOf(this);
  }
}

All structures, constants, enumerations and external functions used in this work are defined in the class Win32Funtions.

Conclusion

This work shows that C# and .NET platform could be an efficient and good solution for low-level tasks like audio extraction or manipulation. If we must do dirty job the better is to use the best tools :-)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here