我已经编写了一个代码段来将 wav 文件刻录到音频 CD 中。它工作正常,但是,在执行验证的最后一步时,它在某些 DVD 驱动器上失败。这些文件实际上被刻录到 CD 中,并且可以毫无问题地播放。但是验证似乎无缘无故地失败了。我可以关闭验证。但是,我更喜欢编写另一个函数来手动检查烧录文件并验证它们是 wav 文件的实际结果。我能够做到这一点来刻录数据 CD。但是对于音频 CD,因为它将它们转换为光盘上的 cda 文件,我无法比较它们。关于如何使用 C# 验证它们的任何建议?基本上让我们假设我有一张包含几个 .cda 文件的音频 CD,我想确保它们是从原始 wav 文件实际转换的文件。
1 回答
将 cda 文件转换为 wav
将 cda 转换为 wav 文件并不容易。
您必须使用一些非托管内存和指针来从 cd 读取数据。
读CD方法:
readcd 过程验证驱动器是 cdrom 驱动器
然后它通过调用
Kernel32 中的 CreateFile 来获取驱动器的句柄。接下来,我们使用该句柄来查看驱动器是否准备好使用
kerenl32 中的 DeviceIoControl 进行读取。如果驱动器准备就绪,那么我们再次使用 DeviceIoControl 查看它是否具有有效的目录(以下称为 TOC)。
如果 TOC 有效,那么我们接下来使用 DeviceIoControl 读取 TOC。
使用 TOC,我们可以确定 CD 上有多少曲目(
这里没有文件 IO;我们已经有了 TOC)。然后在迭代中
,我们继续所有的轨道。我们创建了一个二进制写入器,用于编写二进制文件。
我们将 TOC 跟踪数据转换为名为
TRACK_DATA 的 kernel32 结构。使用该结构,我们能够确定哪个扇区包含该轨道的开头。
而 l 扇区将比下一磁道的起始扇区少一个扇区。旁注:有许多指向结构和字节数组的指针,因此它们之间也有很多来回转换
。磁道大小以扇区数表示,
从结尾减去开始。现在我们遍历该轨道中的所有扇区。
我们创建了一个 kernel32 RAW_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 标头所需的四个 ChunkId。
然后我们初始化三个主要块的各个部分来
表示 PCM、立体声、每秒 44100 个样本以及
表示真实 CD 数据的其他方面。接下来,我们遍历锯齿状
数组 TrackData 中表示的所有轨道。创建一个名为“Track(x).wav”的文件并使用
Kernel32 中的 CreateFile 返回一个句柄。构建标题。
- 添加“音乐”数据。
使用 Kernel32 中的 WriteFile 写入文件。
如果成功则继续。
我们刷新 WriteFile 中使用的所有缓冲区。
我们使用 CloseHandle 关闭文件。
返回并执行下一首曲目,直到全部完成。
现在我们去播放 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
之后,该方法比较所有字节,如果它们都相等则返回成功
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); }
链接: