Creating a CD Player in C#, Part 2: The CD Functions

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!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read