Я написал фрагмент кода для записи файлов wav на аудио компакт-диск. Работает нормально, однако на последнем шаге при выполнении проверки на некоторых DVD-приводах происходит сбой. Файлы фактически записываются на компакт-диск, и их можно воспроизводить без проблем. Но проверка, кажется, не проходит без причины. Я могу отключить проверку. Однако я предпочитаю написать другую функцию, которая вручную проверяет записанные файлы и подтверждает, что они являются фактическим результатом файлов wav. Я смог сделать это для записи компакт-диска с данными. Но для Audio CD, поскольку он преобразует их в файлы cda на диске, я не могу их сравнивать. Любое предложение о том, как проверить их с помощью С#? В общем, давайте предположим, что у меня есть аудио компакт-диск с несколькими файлами .cda, и я хочу убедиться, что они действительно преобразованы из исходных файлов wav. Я знаю, что файлы cda - это просто заполнители, я просто не знаю, как получить из них файлы wav (если это возможно) для сравнения с исходными файлами wav.
Как проверить, что файл cda на Audio CD является результатом файла wav в C#
Ответы (1)
Преобразование файла cda в wav
Не так-то просто преобразовать cda в wav-файл.
Вы должны использовать некоторую неуправляемую память и указатель для чтения данных с компакт-диска.
Метод чтения компакт-диска:
Процедура readcd проверяет, что диск является дисководом компакт-дисков.
Затем он получает дескриптор диска с помощью вызова CreateFile в
Kernel32.Затем мы используем этот дескриптор, чтобы проверить, готов ли диск к чтению, используя
DeviceIoControl в kerenl32.Если диск готов, мы снова смотрим, есть ли у него действительное оглавление (далее — TOC), используя DeviceIoControl.
Если TOC действителен, мы затем читаем TOC с помощью DeviceIoControl.
Используя оглавление, мы определяем, сколько треков может быть на компакт-диске (здесь нет файлового ввода-вывода; у нас уже есть оглавление). Затем в итерации,
просматривая все треки, мы продолжаем.Мы создаем двоичный модуль записи для использования при записи двоичных файлов.
Мы передаем данные дорожки TOC в структуру ядра32 с именем
TRACK_DATA.Используя эту структуру, мы можем определить, какой сектор содержит начало этой дорожки.
И сектор l будет на один сектор меньше, чем начальный сектор следующей дорожки. Примечание: существует множество указателей на структуры и массивы байтов, поэтому между ними также часто выполняется преобразование
туда и обратно.Размер дорожки выражается в количестве секторов путем вычитания start
из end.Теперь проходим по всем секторам этой дорожки.
Мы создаем структуру RAW_READ_INFO ядра32, которая будет использоваться в вызове
DeviceIoControl для чтения сектора.Структура информирует вызов DeviceIoControl о том, что мы читаем компакт-диск и что мы читаем один сектор, в котором он находится на диске. (Помните, что сектора CD немного отличаются от секторов HD; подробнее об этом последнем.)
Теперь читаем этот сектор через DeviceIoControl. Если это было успешно
, мы извлекаем только что прочитанные данные сектора.Поместите данные сектора в соответствующее место в неровном массиве TrackData
.Повторите для всех секторов дорожки.
Повторите для всех дорожек на компакт-диске.
Закройте дескриптор диска с помощью CloseHandle в kerenl32.
// 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:
- Инициализируйте четыре ChunkId, необходимые для заголовка .wav.
Затем мы инициализируем различные части трех основных фрагментов,
представляя PCM, Stereo, 44100 выборок в секунду и другие аспекты,
которые представляют настоящие данные компакт-диска.Затем мы перебираем все дорожки, представленные в массиве TrackData с зубчатой кромкой
.Создайте файл с именем "Track(x).wav" и верните ему дескриптор с помощью
CreateFile в Kernel32.Создайте заголовок.
- Добавьте данные «Музыка».
Запишите файл с помощью WriteFile из Kernel32.
Продолжайте, если это было успешно.
Мы очищаем все буферы, используемые в 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); }
Ссылки:
- https://www.codeproject.com/Questions/509367/comparepluswavplusfilesplusandplusreturnplustheplu
- https://www.c-sharpcorner.com/UploadFile/moosestafa/converting-cda-to-wav/