Click here to Skip to main content
15,881,712 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 447.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

 
GeneralRe: Cd writer Pin
Idael Cardoso11-Oct-04 22:55
Idael Cardoso11-Oct-04 22:55 
GeneralRe: Cd writer Pin
Idael Cardoso13-Oct-04 9:35
Idael Cardoso13-Oct-04 9:35 
GeneralProfile Pin
vonswarengin13-Jul-04 10:57
vonswarengin13-Jul-04 10:57 
GeneralRe: Profile Pin
Idael Cardoso13-Jul-04 23:27
Idael Cardoso13-Jul-04 23:27 
GeneralStill got problem with last track Pin
johnsparrowuk3-Jun-04 6:49
johnsparrowuk3-Jun-04 6:49 
GeneralRe: Still got problem with last track Pin
johnsparrowuk4-Jun-04 7:58
johnsparrowuk4-Jun-04 7:58 
GeneralRe: Still got problem with last track Pin
Idael Cardoso6-Jun-04 9:44
Idael Cardoso6-Jun-04 9:44 
GeneralOnly a 1 kb file is created Pin
Lars Heinemann1-Jun-04 2:41
Lars Heinemann1-Jun-04 2:41 
Hello,

i ported the source to a vb .net code.
actually the program works but when i start ripping i only get a "1 kb" - file created although my cd drive is working really hard.

I don't know where the problem is. I pasted the important source code after it.

Thanks in advance at all.

Regards

Lars Heinemann


--


Dim m_Writer As WaveWriter = Nothing

Public Sub WriteWaveData(ByVal sender As System.Object, ByVal ea As DataReadEventArgs)
If Not m_Writer Is Nothing Then
Dim ds As String = ea.DataSize.ToString
Dim ds2 As Integer = CInt(ds)

m_Writer.Write(ea.Data, 0, ds2)
End If
End Sub
Private Sub CdReadProgress(ByVal sender As System.Object, ByVal ea As ReadProgressEventArgs)
Dim Percent As Double
Dim br As String = Convert.ToString(ea.BytesRead)
Dim br2 As Double = CDbl(br)
Dim b2r As String = Convert.ToString(ea.Bytes2Read)
Dim b2r2 As Double = CDbl(b2r)
Dim res As Double

res = br2 * 100

Percent = res / b2r2
ProgressBar1.Value = CInt(Percent)
Application.DoEvents()

'ea.CancelRead = Me.m_CancelRipping


End Sub
Public Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
If (ListView1.SelectedIndices.Count > 0) Then
Dim track As String = ListView1.SelectedIndices.Item(0) + 1
SaveFileDialog1.FileName = String.Format("track{0:00}.wav", track)
If (SaveFileDialog1.ShowDialog = DialogResult.OK) Then
m_Ripping = True
m_Drive.LockCD()

Dim WaveFormat As New WaveFormat(44100, 16, 2)
Dim WaveFile As New FileStream(SaveFileDialog1.FileName, FileMode.Create, FileAccess.Write)

Dim m_Writer As New WaveWriter(WaveFile, WaveFormat, m_Drive.TrackSize(track))

StatusBar1.Text = String.Format("Reading track {0}", track)

If (m_Drive.ReadTrack(track, New CdDataReadEventHandler(AddressOf WriteWaveData), New CdReadProgressEventHandler(AddressOf CdReadProgress)) > 0) Then
StatusBar1.Text = "Reading track " & track
Else
StatusBar1.Text = "There was an error while reading track " & track
m_Writer.Close()
WaveFile.Close()

If (File.Exists(SaveFileDialog1.FileName)) Then
File.Delete(SaveFileDialog1.FileName)
End If

ProgressBar1.Value = 0

If (m_CancelRipping) Then
m_Ripping = False
Close()
End If
End If

m_Writer.Close()
m_Writer = Nothing
WaveFile.Close()
WaveFile = Nothing

m_Drive.UnLockCD()
m_Ripping = False


End If
End If
End Sub
GeneralRe: Only a 1 kb file is created Pin
Idael Cardoso2-Jun-04 3:48
Idael Cardoso2-Jun-04 3:48 
GeneralProblem with last track of 'enhanced' CD Pin
grahamvhall24-Feb-04 0:52
grahamvhall24-Feb-04 0:52 
GeneralRe: Problem with last track of 'enhanced' CD Pin
Idael Cardoso2-Mar-04 0:08
Idael Cardoso2-Mar-04 0:08 
GeneralRe: Problem with last track of 'enhanced' CD Pin
grahamvhall2-Mar-04 0:58
grahamvhall2-Mar-04 0:58 
GeneralRe: Problem with last track of 'enhanced' CD Pin
ponchorage6-Apr-06 4:54
ponchorage6-Apr-06 4:54 
GeneralFIXED!: Re: Problem with last track of 'enhanced' CD [modified] Pin
Anton Paulic25-May-06 6:07
Anton Paulic25-May-06 6:07 
GeneralUsing this ripper with CDDB Pin
dandante23-Feb-04 13:37
dandante23-Feb-04 13:37 
GeneralRe: Using this ripper with CDDB Pin
dandante23-Feb-04 17:52
dandante23-Feb-04 17:52 
GeneralRe: Using this ripper with CDDB Pin
Idael Cardoso23-Feb-04 23:43
Idael Cardoso23-Feb-04 23:43 
GeneralRe: Using this ripper with CDDB Pin
Delishus21-Mar-04 11:36
Delishus21-Mar-04 11:36 
GeneralRe: Using this ripper with CDDB Pin
Brian Weeres7-Jun-04 11:31
Brian Weeres7-Jun-04 11:31 
GeneralRe: Using this ripper with CDDB Pin
Brian Weeres7-Jun-04 11:59
Brian Weeres7-Jun-04 11:59 
GeneralRe: Using this ripper with CDDB Pin
Taras Tim Bredel3-Oct-04 1:34
Taras Tim Bredel3-Oct-04 1:34 
GeneralRe: Using this ripper with CDDB Pin
Brian Weeres4-Oct-04 3:25
Brian Weeres4-Oct-04 3:25 
GeneralRe: Using this ripper with CDDB Pin
Member 3253794-Oct-04 3:53
Member 3253794-Oct-04 3:53 
GeneralRipping to a mp3 Pin
Manster19-Nov-03 7:49
Manster19-Nov-03 7:49 
GeneralRe: Ripping to a mp3 Pin
Idael Cardoso19-Nov-03 8:48
Idael Cardoso19-Nov-03 8:48 

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.