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.

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:

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.

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:

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:

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.

About the Author

Comments and Discussions

I have been able to add MP3 encoding to the ripper but after much effort I have not been able to add the Lame About.cs and the configuration functions. I am new to C# and probably missing something obvious. I keep getting

The type or name 'About' could not be found ( are you missing a directive or assembly referenc?)

I have copied About.cs to the CDcopier folder and used Project/Add existing item to add this to my project but it still is not recognized.

The form about is defined in another namespace (AudioCompress). You could do one of the following:
- Change the namespace declaration in About.cs
- Add using AudioCompress to the beginning of .cs file where you use the About form.
- Change your code to: AudioCompress.About dialog = new AudioCompress.About();

With the suggestions you sent me I have been able to get About.cs and Config.cs to compile. Many thanks! Unfortunately the Config.cs requires a WAV file to be loaded to work and in my application, a CD ripper, I want to configure before any file is created.

Perhaps I don’t need the Config.cs at all because all I want to do is offer several BIT RATE choices to the user. Is there a command that I can use from the parent program to change the LAME_ENC.DLL bit rate?

I have used your C Sharp Ripper in my examination project. Today I'm just finishing the documentation and since you don't have a Creative Commons license or GPL or something similar attached to your code, I'm not sure how to reference you and your code.

At the moment, my documentation says just that about the component:

For reading and copying audio discs, the application uses the library "C Sharp Ripper" by Idael Cardoso, which is open source and can be found at the following URL:
http://www.codeproject.com/csharp/CSharpRipper.asp

Is that alright for you?

Thanks a lot
coco

_________________________________
Please inform me about my English mistakes, as I'm still trying to learn your language!

I have a little problem copying audio data from a CD:
The size in bytes of the audio track does not match the size of the ripped file.

Do you know this effect and a solution? I need to restore the wave file that had been burned onto the CD, but the ripped wave file is never the same as the original wave file, even though it sounds the same when played in a media player.

Thanks again,
coco

_________________________________
Please inform me about my English mistakes, as I'm still trying to learn your language!

The difference in bytes comes from the way of how the last sector for each track is computed. Just one sector of difference makes a byte’s difference of more than 2K but is only 1/75 seconds, that why it sounds the same even if the are some few sectors of differences.

If the difference is just of some few bytes then it could a different arrangement of data in the WAVE file format (RIFF format).

Hi, I am trying to use your wrappers to read a non-audio track (specifically, I would like to use a DVD but this happens with CD-ROM too). I have changed the TRACK_MODE_TYPE to YellowMode2 and ran a simple ReadTrack command, but I keep getting -1 returned.

It fails at the DeviceIOControl function in all the ReadSector variants, returning 0x7e (The specified module could not be found).

I didn’t try reading a not audio track nor a DVD so I can’t help you so much. But if you are really motivated I advise you to navigate the DDK samples drivers and documentation, you can find many useful information there.

I found the solution for the problem reading the last track. The reason for the fail is that you read one sector too much. Everything works if you don't read the last sector. Actually the bug is the same in all the tracks but doesn't come visible until you read the last track, because all the other tracks return the read sector since the CD consist of it. Only the last track fails reading that sector. By redusing the end sector by one, every thing works. So add this line:

The last track problem could appear in some CD and in son drives and not in others. I made the translation from a C code but I have never had the Red Book so I could not read the exact specification. Of course one sector less or one sector plus don't affect what a human can heard but could be really important in that case.

Thanks you and if you know where can be obtained the Red Book (for free) let me know it.

Hello
The college project im working on at the moment involves reading media, but i ran into a problem. I want to access information from CDs and DVDs. This information includes Type, Capacity, Tracks, Sessions, Manufacturer ID, File System, Title, Date, Publisher, Application.
I thought the process would be similar to accessing the CD Toc, so I searched the online MSDN Library and the internet but i couldn't find the IOCTL 4 byte codes anywhere.

I would very much appreciate any information or advice you have on this matter.

For DVDs I can't help, as far as I know there are not any public API and/or documentation to use. There are some APIs proposed by Microsoft, mainly for DVD playing, in the Windows Media Player Control SDK and in Direct Show but in both cases only works in Windows XP and the information obtained from the DVD could be less than enough

Hello,
Soon I'll provide a translation to IMAPI. At this moment I need to test it before publish it, how IMAPI is only available in Windows XP and I'm ruuning W2k I need to use a PC borrowed from a friend.

Profiles in WmaWriter are a translation to the Windows Media Format SDK profiles. You can find this in the WMF SDK documentation:

"A profile is a collection of data that describes the configuration of an ASF file. At a minimum, a profile must contain configuration settings for a single stream.

The stream information in a profile contains the bit rate, buffer window, and media properties for the stream. The stream information for audio and video describes exactly how the media is configured in the file, including which codec (if any) will be used to compress the data."

But I'm having some trouble reading the last track on a few CD's (not all). And it doesn't happen *all* the time! Only about 1 time in 3. The Error is generated by the ReadSector() method, specifically the

I've also attempted a re-try of the read after Thread.Sleep(50), but no improvement.

It always happens on the last track, but doesn't seem to be a mis-report of length - if I play the mp3 following an error it does indeed end mid-track. (different CD's die at different points, but always on last track).