Introduction
Hello and welcome to the second installment of this series. Now that we have the Windows API functions needed to read and write to a CD and its pits, we need the physical CD functions. This is what you’ll be doing today.
If you haven’t checked out Part 1, “Creating a CD Player in C#, Part 1: The Windows Functions,” of this series yet, I suggest you do so now.
Add a class to your project and name it CDDrive.cls.
Add the next namespaces and class to it.
using System; using System.Runtime.InteropServices; public class clsCDBuffer { byte[] arrBuffer; int intPos = 0; public clsCDBuffer(byte[] aBuffer) { arrBuffer = aBuffer; } public void ReadData(object sender, DataReadEventArgs e) { Buffer.BlockCopy(e.Data, 0, arrBuffer, intPos, (int)e.DataSize); intPos += (int)e.DataSize; } }
This will keep the data that was read.
Inherit from IDisposable and add the following variables to your CDDrive class.
public class CDDrive : IDisposable { private IntPtr ptrCD; private bool blnTOC = false; private Win32API.CDROM_TOC TOC = null; private char chrDrive = ' '; private DeviceChangeNotificationWindow dnNotify = null; public event EventHandler Inserted; public event EventHandler Removed; protected const int NSECTORS = 13; protected const int CB_CDDASECTOR = 2368; protected const int CB_CDROMSECTOR = 2048; protected const int CB_QSUBCHANNEL = 16; protected const int CB_AUDIO = (CB_CDDASECTOR - CB_QSUBCHANNEL); public CDDrive() { TOC = new Win32API.CDROM_TOC(); ptrCD = IntPtr.Zero; } public void Dispose() { Close(); GC.SuppressFinalize(this); }
Add the main events to deal with the opening and closing of the CD drive.
public bool Open(char drive) { Close(); if (Win32API.GetDriveType(drive + ":\") == Win32API.DriveTypes.DRIVE_CDROM) { ptrCD = Win32API.CreateFile("\\.\" + drive + ':', Win32API.GENERIC_READ, Win32API.FILE_SHARE_READ, IntPtr.Zero, Win32API.OPEN_EXISTING, 0, IntPtr.Zero); if (((int)ptrCD != -1) && ((int)ptrCD != 0)) { chrDrive = drive; dnNotify = new DeviceChangeNotificationWindow(); dnNotify.DeviceChange += new DeviceChangeEventHandler(DeviceChange); return true; } else { return true; } } else { return false; } } public void Close() { UnLock(); if (dnNotify != null) { dnNotify.DestroyHandle(); dnNotify = null; } if (((int)ptrCD != -1) && ((int)ptrCD != 0)) { Win32API.CloseHandle(ptrCD); } ptrCD = IntPtr.Zero; chrDrive = ' '; blnTOC = false; } private void OnInserted() { if (Inserted != null) { Inserted(this, EventArgs.Empty); } } private void OnRemoved() { if (Removed != null) { Removed(this, EventArgs.Empty); } } private void DeviceChange(object sender, DeviceChangeEventArgs e) { if (e.Drive == chrDrive) { blnTOC = false; switch (e.ChangeType) { case DeviceChangeEventType.DeviceInserted: OnInserted(); break; case DeviceChangeEventType.DeviceRemoved: OnRemoved(); break; } } } public bool UnLock() { if (((int)ptrCD != -1) && ((int)ptrCD != 0)) { uint Dummy = 0; Win32API.PREVENT_MEDIA_REMOVAL pmr = new Win32API.PREVENT_MEDIA_REMOVAL(); pmr.PreventMediaRemoval = 0; return Win32API.DeviceIoControl(ptrCD, Win32API.IOCTL_STORAGE_MEDIA_REMOVAL, pmr, (uint)Marshal.SizeOf(pmr), IntPtr.Zero, 0, ref Dummy, IntPtr.Zero) != 0; } else { return false; } } public bool Opened { get { return ((int)ptrCD != -1) && ((int)ptrCD != 0); } } public bool Load() { blnTOC = false; if (((int)ptrCD != -1) && ((int)ptrCD != 0)) { uint Dummy = 0; return Win32API.DeviceIoControl(ptrCD, Win32API.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, ref Dummy, IntPtr.Zero) != 0; } else { return false; } } public bool Eject() { blnTOC = false; if (((int)ptrCD != -1) && ((int)ptrCD != 0)) { uint Dummy = 0; return Win32API.DeviceIoControl(ptrCD, Win32API.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, ref Dummy, IntPtr.Zero) != 0; } else { return false; } } public bool Ready() { if (((int)ptrCD != -1) && ((int)ptrCD != 0)) { uint Dummy = 0; if (Win32API.DeviceIoControl(ptrCD, Win32API.IOCTL_STORAGE_CHECK_VERIFY, IntPtr.Zero, 0, IntPtr.Zero, 0, ref Dummy, IntPtr.Zero) != 0) { return true; } else { blnTOC = false; return false; } } else { blnTOC = false; return false; } } public uint TrackSize(int track) { uint Size = 0; ReadTrack(track, null, ref Size, null); return Size; } public int GetTracks() { if (blnTOC) { return TOC.LastTrack - TOC.FirstTrack + 1; } else return -1; }
Add the code to read whatever is on the inserted CD.
protected bool ReadSector(int sect, byte[] bBuffer, int iSectors) { if (blnTOC && ((sect + iSectors) <= EndSector(TOC.LastTrack)) && (bBuffer.Length >= CB_AUDIO * iSectors)) { Win32API.RAW_READ_INFO rri = new Win32API.RAW_READ_INFO(); rri.TrackMode = Win32API.TRACK_MODE_TYPE.CDDA; rri.SectorCount = (uint)iSectors; rri.DiskOffset = sect * CB_CDROMSECTOR; uint uiRead = 0; if (Win32API.DeviceIoControl(ptrCD, Win32API.IOCTL_CDROM_RAW_READ, rri, (uint)Marshal.SizeOf(rri), bBuffer, (uint)iSectors * CB_AUDIO, ref uiRead, IntPtr.Zero) != 0) { return true; } else { return false; } } else { return false; } } public int ReadTrack(int itrack, byte[] Data, ref uint DataSize, uint StartSecond, uint SecondsRead, CdReadProgressEventHandler ProgressEvent) { if (blnTOC && (itrack >= TOC.FirstTrack) && (itrack <= TOC.LastTrack)) { int StartSect = StartSector(itrack); int EndSect = EndSector(itrack); if ((StartSect += (int)StartSecond * 75) >= EndSect) { StartSect -= (int)StartSecond * 75; } if ((SecondsRead > 0) && ((int)(StartSect + SecondsRead * 75) < EndSect)) { EndSect = StartSect + (int)SecondsRead * 75; } DataSize = (uint)(EndSect - StartSect) * CB_AUDIO; if (Data != null) { if (Data.Length >= DataSize) { clsCDBuffer BufferFiller = new clsCDBuffer(Data); return ReadTrack(itrack, new CdDataReadEventHandler(BufferFiller.ReadData), StartSecond, SecondsRead, ProgressEvent); } else { return 0; } } else { return 0; } } else { return -1; } } public int ReadTrack(int itrack, byte[] Data, ref uint DataSize, CdReadProgressEventHandler ProgressEvent) { return ReadTrack(itrack, Data, ref DataSize, 0, 0, ProgressEvent); } public int ReadTrack(int itrack, CdDataReadEventHandler DataReadEvent, uint StartSecond, uint SecondsRead, CdReadProgressEventHandler ProgressEvent) { if (blnTOC && (itrack >= TOC.FirstTrack) && (itrack <= TOC.LastTrack) && (DataReadEvent != null)) { int StartSect = StartSector(itrack); int EndSect = EndSector(itrack); if ((StartSect += (int)StartSecond * 75) >= EndSect) { StartSect -= (int)StartSecond * 75; } if ((SecondsRead > 0) && ((int)(StartSect + SecondsRead * 75) < EndSect)) { EndSect = StartSect + (int)SecondsRead * 75; } uint Bytes2Read = (uint)(EndSect - StartSect) * CB_AUDIO; uint BytesRead = 0; byte[] Data = new byte[CB_AUDIO * NSECTORS]; bool Cont = true; bool ReadOk = true; if (ProgressEvent != null) { ReadProgressEventArgs rpa = new ReadProgressEventArgs(Bytes2Read, 0); ProgressEvent(this, rpa); Cont = !rpa.CancelRead; } for (int sector = StartSect; (sector < EndSect) && (Cont) && (ReadOk); sector += NSECTORS) { int Sectors2Read = ((sector + NSECTORS) < EndSect) ? NSECTORS : (EndSect - sector); ReadOk = ReadSector(sector, Data, Sectors2Read); if (ReadOk) { DataReadEventArgs dra = new DataReadEventArgs(Data, (uint)(CB_AUDIO * Sectors2Read)); DataReadEvent(this, dra); BytesRead += (uint)(CB_AUDIO * Sectors2Read); if (ProgressEvent != null) { ReadProgressEventArgs rpa = new ReadProgressEventArgs(Bytes2Read, BytesRead); ProgressEvent(this, rpa); Cont = !rpa.CancelRead; } } } if (ReadOk) { return (int)BytesRead; } else { return -1; } } else { return -1; } } public int ReadTrack(int itrack, CdDataReadEventHandler DataReadEvent, CdReadProgressEventHandler ProgressEvent) { return ReadTrack(itrack, DataReadEvent, 0, 0, ProgressEvent); } public static char[] DriveLetters() { string res = ""; for (char c = 'C'; c <= 'Z'; c++) { if (Win32API.GetDriveType(c + ":") == Win32API.DriveTypes.DRIVE_CDROM) { res += c; } } return res.ToCharArray(); } protected int StartSector(int track) { if (blnTOC && (track >= TOC.FirstTrack) && (track <= TOC.LastTrack)) { Win32API.TRACK_DATA td = TOC.TrackData[track - 1]; return (td.Address_1 * 60 * 75 + td.Address_2 * 75 + td.Address_3) - 150; } else { return -1; } } protected int EndSector(int track) { if (blnTOC && (track >= TOC.FirstTrack) && (track <= TOC.LastTrack)) { Win32API.TRACK_DATA td = TOC.TrackData[track]; return (td.Address_1 * 60 * 75 + td.Address_2 * 75 + td.Address_3) - 151; } else { return -1; } } public bool Refresh() { if (Ready()) { return ReadTOC(); } else { return false; } } protected bool ReadTOC() { if (((int)ptrCD != -1) && ((int)ptrCD != 0)) { uint BytesRead = 0; blnTOC = Win32API.DeviceIoControl(ptrCD, Win32API.IOCTL_CDROM_READ_TOC, IntPtr.Zero, 0, TOC, (uint)Marshal.SizeOf(TOC), ref BytesRead, IntPtr.Zero) != 0; } else { blnTOC = false; } return blnTOC; } }
Add another class to handle all the events of the CD, and add the following code:
internal enum DeviceChangeEventType { DeviceInserted, DeviceRemoved }; public class DataReadEventArgs : EventArgs { private byte[] bData; private uint uiSize; public DataReadEventArgs(byte[] data, uint size) { bData = data; uiSize = size; } public byte[] Data { get { return bData; } } public uint DataSize { get { return uiSize; } } } public class ReadProgressEventArgs : EventArgs { private uint uiToRead; private uint uiRead; private bool bCancel = false; public ReadProgressEventArgs(uint bytestoread, uint bytesread) { uiToRead = bytestoread; uiRead = bytesread; } public uint BytesRead { get { return uiRead; } } public bool CancelRead { get { return bCancel; } set { bCancel = value; } } } internal class DeviceChangeEventArgs : EventArgs { private DeviceChangeEventType dType; private char cDrive; public DeviceChangeEventArgs(char drive, DeviceChangeEventType type) { cDrive = drive; dType = type; } public char Drive { get { return cDrive; } } public DeviceChangeEventType ChangeType { get { return dType; } } } public delegate void CdDataReadEventHandler(object sender, DataReadEventArgs ea); public delegate void CdReadProgressEventHandler(object sender, ReadProgressEventArgs ea); internal delegate void DeviceChangeEventHandler(object sender, DeviceChangeEventArgs ea); internal enum DeviceType : uint { DBT_DEVTYP_OEM = 0x00000000, DBT_DEVTYP_DEVNODE = 0x00000001, DBT_DEVTYP_VOLUME = 0x00000002, DBT_DEVTYP_PORT = 0x00000003, DBT_DEVTYP_NET = 0x00000004 } internal enum VolumeChangeFlags : ushort { DBTF_MEDIA = 0x0001, DBTF_NET = 0x0002 } internal struct DEV_BROADCAST_HDR { public uint dbch_size; public DeviceType dbch_devicetype; uint dbch_reserved; } [StructLayout(LayoutKind.Sequential)] internal struct DEV_BROADCAST_VOLUME { public uint dbcv_size; public DeviceType dbcv_devicetype; uint dbcv_reserved; uint dbcv_unitmask; public char[] Drives { get { string drvs = ""; for (char c = 'A'; c <= 'Z'; c++) { if ((dbcv_unitmask & (1 << (c - 'A'))) != 0) { drvs += c; } } return drvs.ToCharArray(); } } public VolumeChangeFlags dbcv_flags; } internal class DeviceChangeNotificationWindow : NativeWindow { public event DeviceChangeEventHandler DeviceChange; const int WS_EX_TOOLWINDOW = 0x80; const int WS_POPUP = unchecked((int)0x80000000); const int WM_DEVICECHANGE = 0x0219; const int DBT_APPYBEGIN = 0x0000; const int DBT_APPYEND = 0x0001; const int DBT_DEVNODES_CHANGED = 0x0007; const int DBT_QUERYCHANGECONFIG = 0x0017; const int DBT_CONFIGCHANGED = 0x0018; const int DBT_CONFIGCHANGECANCELED = 0x0019; const int DBT_MONITORCHANGE = 0x001B; const int DBT_SHELLLOGGEDON = 0x0020; const int DBT_CONFIGMGAPI32 = 0x0022; const int DBT_VXDINITCOMPLETE = 0x0023; const int DBT_VOLLOCKQUERYLOCK = 0x8041; const int DBT_VOLLOCKLOCKTAKEN = 0x8042; const int DBT_VOLLOCKLOCKFAILED = 0x8043; const int DBT_VOLLOCKQUERYUNLOCK = 0x8044; const int DBT_VOLLOCKLOCKRELEASED = 0x8045; const int DBT_VOLLOCKUNLOCKFAILED = 0x8046; const int DBT_DEVICEARRIVAL = 0x8000; const int DBT_DEVICEQUERYREMOVE = 0x8001; const int DBT_DEVICEQUERYREMOVEFAILED = 0x8002; const int DBT_DEVICEREMOVEPENDING = 0x8003; const int DBT_DEVICEREMOVECOMPLETE = 0x8004; const int DBT_DEVICETYPESPECIFIC = 0x8005; public DeviceChangeNotificationWindow() { CreateParams Params = new CreateParams(); Params.ExStyle = WS_EX_TOOLWINDOW; Params.Style = WS_POPUP; CreateHandle(Params); } private void OnCDChange(DeviceChangeEventArgs ea) { if (DeviceChange != null) { DeviceChange(this, ea); } } private void OnDeviceChange(DEV_BROADCAST_VOLUME DevDesc, DeviceChangeEventType EventType) { if (DeviceChange != null) { foreach (char ch in DevDesc.Drives) { DeviceChangeEventArgs a = new DeviceChangeEventArgs(ch, EventType); DeviceChange(this, a); } } } protected override void WndProc(ref Message m) { if (m.Msg == WM_DEVICECHANGE) { DEV_BROADCAST_HDR head; switch (m.WParam.ToInt32()) { case DBT_DEVICEARRIVAL: head = (DEV_BROADCAST_HDR)Marshal.PtrToStructure (m.LParam, typeof(DEV_BROADCAST_HDR)); if (head.dbch_devicetype == DeviceType.DBT_DEVTYP_VOLUME) { DEV_BROADCAST_VOLUME DevDesc = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure (m.LParam, typeof(DEV_BROADCAST_VOLUME)); if (DevDesc.dbcv_flags == VolumeChangeFlags.DBTF_MEDIA) { OnDeviceChange(DevDesc, DeviceChangeEventType.DeviceInserted); } } break; case DBT_DEVICEREMOVECOMPLETE: head = (DEV_BROADCAST_HDR)Marshal.PtrToStructure (m.LParam, typeof(DEV_BROADCAST_HDR)); if (head.dbch_devicetype == DeviceType.DBT_DEVTYP_VOLUME) { DEV_BROADCAST_VOLUME DevDesc = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure (m.LParam, typeof(DEV_BROADCAST_VOLUME)); if (DevDesc.dbcv_flags == VolumeChangeFlags.DBTF_MEDIA) { OnDeviceChange(DevDesc, DeviceChangeEventType.DeviceRemoved); } } break; } } base.WndProc(ref m); }
Conclusion
In the next installment, we will finalize this series. Until then, stay in practise!