|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis 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 ( 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 BackgroundIn 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 Using the codeThe main code is a class library named ripper. The class 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 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 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 #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 [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 [ 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 ConclusionThis 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 :-)
|
||||||||||||||||||||||