Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

C Sharp Ripper

Rate me:
Please Sign up or sign in to vote.
4.89/5 (58 votes)
13 Jan 20045 min read 446.1K   9.7K   214   92
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:

cs"
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:

C#
#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:

C#
[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:

C#
[ 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


Written By
Web Developer
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSingle track to single file Pin
paolo guccini24-Aug-15 9:49
professionalpaolo guccini24-Aug-15 9:49 
QuestionMay I use Ripper.DLL project in derived work? Pin
Sergey Alexandrovich Kryukov19-Dec-13 9:23
mvaSergey Alexandrovich Kryukov19-Dec-13 9:23 
QuestionWhat about obtaining other infomation like song names, titles, track length? Pin
Ted Mad5-Aug-13 8:55
Ted Mad5-Aug-13 8:55 
AnswerRe: What about obtaining other infomation like song names, titles, track length? Pin
Sergey Alexandrovich Kryukov11-Dec-13 13:59
mvaSergey Alexandrovich Kryukov11-Dec-13 13:59 
GeneralMy vote of 5 Pin
Alberto M.24-Jul-12 6:13
Alberto M.24-Jul-12 6:13 
great!
GeneralMy vote of 5 Pin
Manoj Kumar Choubey12-Feb-12 23:28
professionalManoj Kumar Choubey12-Feb-12 23:28 
GeneralMy vote of 5 Pin
VermaManish1-Dec-11 6:25
VermaManish1-Dec-11 6:25 
QuestionSlow down song. Pin
Khiev Kim Khun2-Jan-11 23:28
professionalKhiev Kim Khun2-Jan-11 23:28 
AnswerRe: Slow down song. Pin
thefiloe5-Oct-12 7:50
thefiloe5-Oct-12 7:50 
QuestionHow can I get track duration ? Pin
Khiev Kim Khun2-Jan-11 19:14
professionalKhiev Kim Khun2-Jan-11 19:14 
AnswerRe: How can I get track duration ? Pin
Lukas Molnar15-Feb-11 0:03
Lukas Molnar15-Feb-11 0:03 
GeneralRe: How can I get track duration ? Pin
Khiev Kim Khun15-Feb-11 16:08
professionalKhiev Kim Khun15-Feb-11 16:08 
GeneralRe: How can I get track duration ? Pin
Medlan27-May-11 22:50
Medlan27-May-11 22:50 
GeneralJitter Correction Pin
dawnc212-Jun-09 14:01
dawnc212-Jun-09 14:01 
QuestionIs it possible to retrieve subcode information with DeviceIoControl? Pin
tmbrye11-Feb-08 13:11
tmbrye11-Feb-08 13:11 
GeneralCD+G Pin
SreeniTheGinie11-Nov-07 18:17
SreeniTheGinie11-Nov-07 18:17 
GeneralRe: CD+G Pin
Sergey Alexandrovich Kryukov19-Dec-13 17:21
mvaSergey Alexandrovich Kryukov19-Dec-13 17:21 
AnswerI think you can try (Re: CD+G ) Pin
Sergey Alexandrovich Kryukov19-Dec-13 17:22
mvaSergey Alexandrovich Kryukov19-Dec-13 17:22 
QuestionHow would you write the set method for TrackDataList Pin
Ankit Parikh5-Nov-07 12:12
Ankit Parikh5-Nov-07 12:12 
Generaladmin required on vista Pin
tunedude7-Jul-07 5:46
tunedude7-Jul-07 5:46 
Generalproblem reading audio track Pin
h82w824-Jan-07 19:11
h82w824-Jan-07 19:11 
GeneralRe: problem reading audio track Pin
SALEEM MULLA28-Dec-08 19:49
SALEEM MULLA28-Dec-08 19:49 
GeneralRe: problem reading audio track on Enhanced CDs Pin
elliz21-Jan-09 5:40
elliz21-Jan-09 5:40 
GeneralRe: problem reading last audio track on enhanced CDs - SOLVED / HACK Pin
elliz21-Jan-09 7:43
elliz21-Jan-09 7:43 
GeneralRe: problem reading last audio track on enhanced CDs - SOLVED / HACK Pin
Medlan27-May-11 22:52
Medlan27-May-11 22:52 

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.