I have written a code segment to burn wav files into an Audio CD. It works fine, however, at the last step when performing verification, it fails on some DVD drives. The files are actually burnt into CD and they can be played with no issues. But the verification seems to be failing for no reason. I can turn off verification. However, I prefer to write another function that manually checks the burnt files and verifies that they are the actual result of wav files. I was able to do that for burning data CD. But for Audio CD, as it converts them into cda files on disc, I can't compare them. Any suggestion on how to verify them using C#? Basically let's assume that I have an Audio CD with a few .cda files in it, and I want to make sure they are the actual converted files from the original wav files. I do know that cda files are just placeholders, I just don't know how to get the wav files (if it is possible) out of them to compare with the original wav files.
Converting a cda file to wav
It's not that easy converting a cda to wav file.
You have to use some unmanaged memory and pointer to read data from the cd.
ReadCD method:
The readcd procedure verifies that the drive is a cdrom drive
It then gets a handle to the drive via a call to CreateFile in
Kernel32.
Next we use that handle to see if the drive is ready to read using
DeviceIoControl in kerenl32.
If the drive is ready then we see if it has a valid Table of Contents (hereafter refereed to as TOC) again using DeviceIoControl.
If the TOC is valid then we next read the TOC using DeviceIoControl.
Using the TOC we determine how may tracks there are on the CD (no
file IO here; we already have the TOC). Then in an iteration though
all the tracks we proceed.
We create a binary writer for use in writing the binary files.
We cast the TOC track data into a kernel32 structure called
TRACK_DATA.
Using that structure we are able to determine which sector holds the beginning of that track.
And the l sector will be one sector less than the start sector of the
next track. Side note: There are many pointers to structures and byte
arrays so there is also a lot of converting back and forth between
them.
Track size is expressed in number of sectors by subtracting start
from end.
Now we iterate through all sectors in this track.
We create a kernel32 RAW_READ_INFO structure to be used in the
DeviceIoControl call to read the sector.
The structure informs the DeviceIoControl call that we are reading a CD and that we are reading one sector where he sector is on the disc. (Remember CD sectors are a little different than HD sectors; more on that latter.)
Now we read that sector via DeviceIoControl. If it was successful
then we retrieve the sector data just read.
Put the sector data into the appropriat4e place in the TrackData
jagged array.
Repeat for all sectors in the track.
Repeat for all tracks on the CD.
Close the handle to the drive using CloseHandle in 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 method:
Then we initialize the various parts of the three main chunks to
represent PCM, Stereo, 44100 Samples per second, and other aspects
that represent true CD data.
Next, we iterate through all tracks as represented in the jagged
array TrackData.
Create a file called "Track(x).wav" and return a handle to it using
CreateFile in Kernel32.
Build the Header.
Write the file using WriteFile from Kernel32.
Proceed if that was successful.
We flush all buffers used in WriteFile.
And we close the file using CloseHandle.
Return and do the next track until they are all done.
And now we go play the wav files and gloat over how cooked we are
// 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;
}
}
File compare
This method has a validation before all bytes are checked for equality.
After that the method compares all bytes and returns the success if all of them equals
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);
}
Links: