Click here to Skip to main content
Click here to Skip to main content

C Sharp Ripper

, 13 Jan 2004
Rate this:
Please Sign up or sign in to vote.
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 Smile | :) 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 Smile | :)

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

Share

About the Author

Idael Cardoso
Web Developer
France France
No Biography provided

Comments and Discussions

 
AnswerRe: Adding About and Configure to Ripper PinmemberIdael Cardoso10-Mar-06 8:33 
GeneralRe: Adding About and Configure to Ripper Pinmemberelectriac11-Mar-06 2:44 
GeneralNaming you in my project's documentation PinmemberCorinna John3-Feb-06 3:07 
AnswerRe: Naming you in my project's documentation PinmemberIdael Cardoso5-Mar-06 8:11 
GeneralWrong file size PinmemberCorinna John21-Aug-05 7:55 
GeneralRe: Wrong file size PinmemberIdael Cardoso4-Sep-05 0:50 
GeneralMarshaling array of struct inside struct Pinmemberkreimern31-Jul-05 20:20 
GeneralRe: Marshaling array of struct inside struct PinmemberIdael Cardoso7-Aug-05 6:52 
Hi,
 
Marshal.SizeOf(typeof(Win32Functions.TRACK_DATA)) == 8
 
But C# attribute arguments must be a constant expression, typeof expression or array creation expression, that is why you found 8 in my code and not the more clear expression that you suggested.
 
Regards,
Idael.
GeneralReading non-audio tracks Pinmemberwhoop_whoop8-Mar-05 0:21 
GeneralRe: Reading non-audio tracks PinmemberIdael Cardoso19-Apr-05 8:16 
GeneralSolution for problem reading the last track Pinmemberkjetilroe25-Oct-04 0:13 
GeneralRe: Solution for problem reading the last track PinmemberIdael Cardoso11-Nov-04 7:56 
GeneralRe: Solution for problem reading the last track PinmemberJack Kallestrup20-Jan-06 8:39 
GeneralAccessing CD and DVD header info Pinmembertheindescribablehunk16-Oct-04 14:29 
GeneralRe: Accessing CD and DVD header info PinmemberIdael Cardoso18-Oct-04 0:39 
GeneralCd writer Pinmemberkbrryder7-Oct-04 14:46 
GeneralRe: Cd writer PinmemberIdael Cardoso11-Oct-04 22:55 
GeneralRe: Cd writer PinmemberIdael Cardoso13-Oct-04 9:35 
GeneralProfile Pinmembervonswarengin13-Jul-04 10:57 
GeneralRe: Profile PinmemberIdael Cardoso13-Jul-04 23:27 
GeneralStill got problem with last track Pinmemberjohnsparrowuk3-Jun-04 6:49 
GeneralRe: Still got problem with last track Pinmemberjohnsparrowuk4-Jun-04 7:58 
GeneralRe: Still got problem with last track PinmemberIdael Cardoso6-Jun-04 9:44 
GeneralOnly a 1 kb file is created PinmemberLars Heinemann1-Jun-04 2:41 
GeneralRe: Only a 1 kb file is created PinmemberIdael Cardoso2-Jun-04 3:48 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.141022.2 | Last Updated 14 Jan 2004
Article Copyright 2003 by Idael Cardoso
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid