如何在 C# 中验证音频 CD 上的 cda 文件是 wav 文件的结果

How to verify that a cda file on Audio CD is the result of a wav file in C#

我已经编写了一个代码段来将 wav 文件刻录到音频 CD 中。 它工作正常,但是,在执行验证的最后一步,它在某些 DVD 驱动器上失败。这些文件实际上已刻录到 CD 中,可以毫无问题地播放。但是验证似乎无缘无故地失败了。我可以关闭验证。但是,我更喜欢编写另一个函数来手动检查烧录的文件并验证它们是否是 wav 文件的实际结果。我能够做到这一点来刻录数据 CD。但是对于音频CD,因为它将它们转换成光盘上的cda文件,所以我无法比较它们。关于如何使用 C# 验证它们的任何建议?基本上让我们假设我有一张音频 CD,里面有几个 .cda 文件,我想确保它们是原始 wav 文件的实际转换文件。我知道 cda 文件只是占位符,我只是不知道如何从中获取 wav 文件(如果可能的话)以与原始 wav 文件进行比较。

正在将 cda 文件转换为 wav

将 cda 文件转换为 wav 文件并不是那么容易。

您必须使用一些非托管内存和指针才能从 cd 读取数据。


读CD方法:

  • readcd 程序验证驱动器是否为 cdrom 驱动器

  • 然后通过在
    中调用 CreateFile 获取驱动器句柄 Kernel32.

  • 接下来我们使用该句柄查看驱动器是否已准备好使用
    读取 kerenl32 中的 DeviceIoControl。

  • 如果驱动器准备就绪,那么我们会查看它是否具有有效的 Table 内容 (以下简称 TOC)再次使用 DeviceIoControl.

  • 如果 TOC 有效,那么接下来我们使用 DeviceIoControl 读取 TOC。

  • 使用 TOC 我们可以确定 CD 上有多少曲目(没有
    在这里提交 IO;我们已经有了目录)。然后在迭代中
    我们进行的所有曲目。

  • 我们创建一个二进制写入器用于写入二进制文件。

  • 我们将 TOC 轨道数据转换为一个名为
    的 kernel32 结构 TRACK_DATA.

  • 使用该结构,我们能够确定哪个部门拥有 该曲目的开头。

  • 而第l扇区会比第l扇区的起始扇区少一个扇区 下一首曲目。旁注:有很多指向结构和字节的指针 数组所以在
    之间也有很多来回转换 他们。

  • 磁道大小以扇区数减去开始
    表示 从头开始。

  • 现在我们遍历该轨道中的所有扇区。

  • 我们创建了一个kernel32RAW_READ_INFO结构,用于
    读取扇区的 DeviceIoControl 调用。

  • 该结构通知 DeviceIoControl 调用我们正在读取一个 CD 并且我们正在读取一个扇区,该扇区位于光盘上。 (记住 CD 扇区与 HD 扇区略有不同;更多关于 后者。)

  • 现在我们通过 DeviceIoControl 读取该扇区。如果成功
    然后我们检索刚刚读取的扇区数据。

  • 将扇区数据放入TrackData中合适的位置
    锯齿状数组。

  • 重复轨道中的所有扇区。

  • 重复 CD 上的所有曲目。

  • 使用 kerenl32 中的 CloseHandle 关闭驱动器的句柄。

     // this functions reads binary audio data from a cd and stores it in a jagged array called TrackData
    
    // it uses only low level file io calls to open and read the Table of Content and then the binary 'music' data sector by sector
    
    // as discovered from the table of content
    
    // it also writes it to a binary file called tracks with not extension
    
    // this file can be read by any decent hex editor
    
    void readcd()
    
    {
    
        bool TocValid = false;
    
        IntPtr cdHandle = IntPtr.Zero;
    
        CDROM_TOC Toc = null;
    
        int track, StartSector, EndSector;
    
        BinaryWriter bw;
    
        bool CDReady;
    
        uint uiTrackCount, uiTrackSize, uiDataSize;
    
        int i;
    
        uint BytesRead, Dummy;
    
        char Drive = (char)cmbDrives.Text[0];
    
        TRACK_DATA td;
    
        int sector;
    
        byte[] SectorData;
    
        IntPtr pnt;
    
        Int64 Offset;
    
    
    
        btnStart.Enabled = false;
    
    
    
        Dummy = 0;
    
        BytesRead = 0;
    
        CDReady = false;
    
    
    
        Toc = new CDROM_TOC();
    
        IntPtr ip = Marshal.AllocHGlobal((IntPtr)(Marshal.SizeOf(Toc)));
    
        Marshal.StructureToPtr(Toc, ip, false);
    
        // is it a cdrom drive
    
        DriveTypes dt = GetDriveType(Drive + ":\");
    
        if (dt == DriveTypes.DRIVE_CDROM)
    
        {
    
            // get a Handle to control the drive with
    
            cdHandle = CreateFile("\\.\" + Drive + ':', GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
    
            CDReady = DeviceIoControl(cdHandle, IOCTL_STORAGE_CHECK_VERIFY, IntPtr.Zero, 0, IntPtr.Zero, 0, ref Dummy, IntPtr.Zero) == 1;
    
            if (!CDReady)
    
            {
    
                MessageBox.Show("Drive Not Ready", "Drive Not Ready", MessageBoxButtons.OK);
    
    
    
            }
    
            else
    
            {
    
                uiTrackCount = 0;
    
                // is the Table of Content valid?
    
                TocValid = DeviceIoControl(cdHandle, IOCTL_CDROM_READ_TOC, IntPtr.Zero, 0, ip, (uint)Marshal.SizeOf(Toc), ref BytesRead, IntPtr.Zero) != 0;
    
                //fetch the data from the unmanaged pointer back to the managed structure
    
                Marshal.PtrToStructure(ip, Toc);
    
                if (!TocValid)
    
                {
    
                    MessageBox.Show("Invalid Table of Content ", "Invalid Table of Content ", MessageBoxButtons.OK);
    
                }
    
                else
    
                {
    
                    // really only nescary if there are un-useable tracks
    
                    uiTrackCount = Toc.LastTrack;
    
                    //for (i = Toc.FirstTrack - 1; i < Toc.LastTrack; i++)
    
                    //{
    
                    //    if (Toc.TrackData[i].Control == 0)
    
                    //        uiTrackCount++;
    
                    //}
    
                    // create a jagged array to store the track data
    
                    TrackData = new byte[uiTrackCount][];
    
    
    
    
    
                    // read all the tracks
    
                    for (track = 1; track <= uiTrackCount; track++)//uiTrackCount; track++)
    
                    {
    
                        Offset = 0;// used to store Sectordata into trackdata
    
                        label1.Text = "Reading Track" + track.ToString() + " of " + uiTrackCount.ToString(); ;
    
                        Application.DoEvents();
    
                        // create a binary writer to write the track data
    
                        bw = new BinaryWriter(File.Open(Application.StartupPath + "\Track" + track.ToString (), FileMode.Create));
    
    
    
                        //The CDROM_TOC-structure contains the FirstTrack (1) and the LastTrack (max. track nr). CDROM_TOC::TrackData[0] contains info of the
    
                        //first track on the CD. Each track has an address. It represents the track's play-time using individual members for the hour, minute,
    
                        //second and frame. The "frame"-value (Address[3]) is given in 1/75-parts of a second -> Remember: 75 frames form one second and one
    
                        //frame occupies one sector.
    
    
    
                        //Find the first and last sector of the track
    
                        td = Toc.TrackData[track - 1];
    
                        //              minutes                   Seconds       fractional seconds     150 bytes is the 2 second lead in to track 1
    
                        StartSector = (td.Address_1 * 60 * 75 + td.Address_2 * 75 + td.Address_3) - 150;
    
                        td = Toc.TrackData[track];
    
                        EndSector = (td.Address_1 * 60 * 75 + td.Address_2 * 75 + td.Address_3) - 151;
    
                        progressBar1.Minimum = StartSector;
    
                        progressBar1.Maximum = EndSector;
    
                        uiTrackSize = (uint)(EndSector - StartSector) * CB_AUDIO;//CB_AUDIO==2352
    
                        // how big is the track
    
                        uiDataSize = (uint)uiTrackSize;
    
                        //Allocate for the track
    
                        TrackData[track - 1] = new byte[uiDataSize];
    
                        SectorData = new byte[CB_AUDIO * NSECTORS];
    
    
    
                        // read all the sectors for this track
    
                        for (sector = StartSector; (sector < EndSector); sector += NSECTORS)
    
                        {
    
                            Debug.Print(sector.ToString("X2"));
    
                            RAW_READ_INFO rri = new RAW_READ_INFO();// contains info about the sector to be read
    
                            rri.TrackMode = TRACK_MODE_TYPE.CDDA;
    
                            rri.SectorCount = (uint)1;
    
                            rri.DiskOffset = sector * CB_CDROMSECTOR;
    
                            //get a pointer to the structure
    
                            Marshal.StructureToPtr(rri, ip, false);
    
                            // allocate an unmanged pointer to hold the data read from the disc
    
                            int size = Marshal.SizeOf(SectorData[0]) * SectorData.Length;
    
                            pnt = Marshal.AllocHGlobal(size);
    
    
    
                            //Sector data is a byte array to hold data from each sector data
    
                            // initiallize it to all zeros
    
                            SectorData.Initialize();
    
    
    
    
    
                            // read the sector
    
                            i = DeviceIoControl(cdHandle, IOCTL_CDROM_RAW_READ, ip, (uint)Marshal.SizeOf(rri), pnt, (uint)NSECTORS * CB_AUDIO, ref BytesRead, IntPtr.Zero);
    
                            if (i == 0)
    
                            {
    
                                MessageBox.Show("Bad Sector Read", "Bad Sector Read from sector " + sector.ToString("X2"), MessageBoxButtons.OK);
    
                                break;
    
                            }
    
                            progressBar1.Value = sector;                             // return the pointers to their respective managed data sources
    
                            Marshal.PtrToStructure(ip, rri);
    
                            Marshal.Copy(pnt, SectorData, 0, SectorData.Length);
    
    
    
                            Marshal.FreeHGlobal(pnt);
    
                            Array.Copy(SectorData, 0, TrackData[track - 1], Offset, BytesRead);
    
                            Offset += BytesRead;
    
                        }
    
    
    
                        // write the binary data nad then close it
    
                        bw.Write(TrackData[track - 1]);
    
                        bw.Close();
    
                    }
    
                    //unlock
    
                    PREVENT_MEDIA_REMOVAL pmr = new PREVENT_MEDIA_REMOVAL();
    
                    pmr.PreventMediaRemoval = 0;
    
                    ip = Marshal.AllocHGlobal((IntPtr)(Marshal.SizeOf(pmr)));
    
                    Marshal.StructureToPtr(pmr, ip, false);
    
                    DeviceIoControl(cdHandle, IOCTL_STORAGE_MEDIA_REMOVAL, ip, (uint)Marshal.SizeOf(pmr), IntPtr.Zero, 0, ref Dummy, IntPtr.Zero);
    
                    Marshal.PtrToStructure(ip, pmr);
    
                    Marshal.FreeHGlobal(ip);
    
                }
    
            }
    
        }
    
        //Close the CD Handle
    
        CloseHandle(cdHandle);
    
        ConvertToWav();
    
    }  
    

ConvertToWav 方法:

  • 初始化 .wav 所需的四个 ChunkIds header。
  • 然后我们将三个主要chunk的各个部分初始化为
    代表PCM、Stereo、44100 Samples per second等方面
    代表真正的 CD 数据。

  • 接下来,我们遍历锯齿状
    中表示的所有轨道 数组 TrackData.

  • 创建一个名为 "Track(x).wav" 的文件,并使用
    return 它的句柄 在 Kernel32.CreateFile.

  • 构建 Header.

  • 添加 "Music" 数据。
  • 使用 Kernel32 中的 WriteFile 写入文件。

  • 如果成功则继续。

  • 我们刷新 WriteFile 中使用的所有缓冲区。

  • 然后我们使用 CloseHandle 关闭文件。

  • Return 并进行下一曲,直到全部完成。

  • 现在我们去播放 wav 文件并幸灾乐祸我们有多熟

        // this procedure tacks the biary data stored in the jagged array called TraackData
        // and, using low level file io functions) writes it out as a .wav file called trackx.wav
    private void ConvertToWav()
    {
    
        int i, j, k, track, tracks;
    
        byte[] b;
    
        char[] riffchunk ={ 'R', 'I', 'F', 'F' };
    
        char[] wavechunk ={ 'W', 'A', 'V', 'E' };
    
        char[] datachunk ={ 'd', 'a', 't', 'a' };
    
        char[] fmtchunk ={ 'f', 'm', 't', ' ' };
    
        Int32 riffsize, datasize, fmtsize, extrabits;
    
        Int32 DI, SampleRate, ByteRate;
    
        uint BytesWritten;
    
        Int16 BlockAlign, Format, NumChannels, BitsPerSample;
    
        Byte[] Image;
    
        IntPtr FileHandle;
    
    
    
        Format = 1; // PCM
    
        NumChannels = 2;// Stereo
    
        SampleRate = 44100;// 44100 Samples per secon
    
        BitsPerSample = 16; // 16 bits per sample
    
        ByteRate = SampleRate * NumChannels * BitsPerSample / 8;
    
        BlockAlign = 4;
    
        fmtsize = 0x12;// size of the 'fmt ' chunk is 18 bytes
    
        // get the number of tarcks stoerd in track data
    
        tracks = TrackData.GetUpperBound(0);
    
        // setup the progressbar
    
        progressBar1.Maximum = tracks;
    
        progressBar1.Minimum = 0;
    
        // do all the tracks
    
        for (track = 0; track <= tracks; track++)
    
        {
    
            DI = 0;//IDI is an index into the Image array where the next chunk of data will be stored
    
            progressBar1.Value = track;
    
            label1.Text = "Writeing Track " + (track + 1).ToString() + ".wav";
    
            Application.DoEvents();
    
            // Create a File called trackx.wav and return a handle to it
    
            FileHandle=CreateFile(Application.StartupPath + "\Track" + (track + 1).ToString() + ".wav",GENERIC_WRITE,0,IntPtr.Zero ,OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero );
    
            // Wav file format is notthe subject of this project .. .
    
            // suffice it to say that at minimum there is a Header which is followed by the PCM, Stereo , 44100 Hz Sample rate binary data
    
            // for more info on Wav format plese visit:
    
            //http://www.sonicspot.com/guide/wavefiles.html
    
    
    
            //Start prepareing the RIFF header
    
    
    
            // how big the the 'music' binary data
    
            datasize = TrackData[track].Length;
    
            //build the header
    
            riffsize = datasize;
    
            riffsize += 4;//RIFFSize
    
            riffsize += 4;//WAVE
    
            riffsize += 4;//fmt
    
            riffsize += fmtsize;
    
            riffsize += 4;// DATA
    
            riffsize += 4;//datasize
    
            extrabits = 0;
    
            // build the image
    
            Image = new Byte[riffsize + 8];// riffchunk + riffsize
    
            b = Encoding.ASCII.GetBytes(riffchunk);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
            b = BitConverter.GetBytes(riffsize);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
            b = Encoding.ASCII.GetBytes(wavechunk);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
    
    
            b = Encoding.ASCII.GetBytes(fmtchunk);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
    
    
            b = BitConverter.GetBytes(fmtsize);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
    
    
            b = BitConverter.GetBytes(Format);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 2);
    
            DI += 2;
    
    
    
            b = BitConverter.GetBytes(NumChannels);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 2);
    
            DI += 2;
    
    
    
            b = BitConverter.GetBytes(SampleRate);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
    
    
            b = BitConverter.GetBytes(ByteRate);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
    
    
            b = BitConverter.GetBytes(BlockAlign);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 2);
    
            DI += 2;
    
    
    
            b = BitConverter.GetBytes(BitsPerSample);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 2);
    
            DI += 2;
    
    
    
            b = BitConverter.GetBytes(extrabits);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 2);
    
            DI += 2;
    
    
    
            b = Encoding.ASCII.GetBytes(datachunk);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
    
    
            b = BitConverter.GetBytes(datasize);
    
            if (!BitConverter.IsLittleEndian)
    
                Array.Reverse(b);
    
            Array.Copy(b, 0, Image, DI, 4);
    
            DI += 4;
    
    
    
            // add the digital 'music' data retrieved earler
    
            Array.Copy(TrackData[track], 0, Image, DI, TrackData[track].Length);
    
            // write the binary file - trackx.wav
    
            i = WriteFile(FileHandle, Image, (uint)Image.Length, out BytesWritten, IntPtr.Zero);
    
            //if successful then
    
            // flush all buffers used in the low level write operation
    
            // then close the file
    
            if(i!= 0)
    
            {
    
                //Flush the file buffers to force writing of the data.
    
                i = FlushFileBuffers(FileHandle);
    
                //Close the file.
    
                i = CloseHandle(FileHandle);
    
            }
    
            // the wave file now exists (created by reading the CD and can be playedby most wav players
    
            Image = null;
    
            progressBar1.Value = track;
    
        }
    
    }
    

文件比较

此方法在检查所有字节是否相等之前进行验证。

  • 当两个文件的长度不同时,它们不可能是同一个文件,false 将被 returned
  • 之后,该方法比较所有字节,return如果所有字节都等于

    ,则成功
    private bool FileCompare(string file1, string file2)
    {
     int file1byte;
     int file2byte;
     FileStream fs1;
     FileStream fs2;
    
     // Open the two files.
     fs1 = new FileStream(file1, FileMode.Open);
     fs2 = new FileStream(file2, FileMode.Open);
    
     // Check the file sizes. If they are not the same, the files
        // are not the same.
     if (fs1.Length != fs2.Length)
     {
          // Close the file
          fs1.Close();
          fs2.Close();
    
          // Return false to indicate files are different
          return false;
     }
    
     // Read and compare a byte from each file until either a
     // non-matching set of bytes is found or until the end of
     // file1 is reached.
     do
     {
          // Read one byte from each file.
          file1byte = fs1.ReadByte();
          file2byte = fs2.ReadByte();
     }
     while ((file1byte == file2byte) && (file1byte != -1));
    
     // Close the files.
     fs1.Close();
     fs2.Close();
    
     // Return the success of the comparison. "file1byte" is
     // equal to "file2byte" at this point only if the files are
        // the same.
     return ((file1byte - file2byte) == 0);
     }
    

链接: