Created
March 13, 2010 21:23
-
-
Save sbooth/331559 to your computer and use it in GitHub Desktop.
Accurate Rip CRC utility
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <CoreFoundation/CoreFoundation.h> | |
#include <CoreServices/CoreServices.h> | |
#include <AudioToolbox/AudioFile.h> | |
#include <IOKit/storage/IOCDTypes.h> | |
#define AUDIO_FRAMES_PER_CDDA_SECTOR 588 | |
#define CDDA_SAMPLE_RATE 44100 | |
#define CDDA_CHANNELS_PER_FRAME 2 | |
#define CDDA_BITS_PER_CHANNEL 16 | |
// ======================================== | |
// Verify an AudioStreamBasicDescription describes CDDA audio | |
// ======================================== | |
bool | |
streamDescriptionIsCDDA(const AudioStreamBasicDescription *asbd) | |
{ | |
// NSCParameterAssert(NULL != asbd); | |
if(kAudioFormatLinearPCM != asbd->mFormatID) | |
return false; | |
if(!(kAudioFormatFlagIsSignedInteger & asbd->mFormatFlags) || !((kAudioFormatFlagIsPacked & asbd->mFormatFlags))) | |
return false; | |
if(CDDA_SAMPLE_RATE != asbd->mSampleRate) | |
return false; | |
if(CDDA_CHANNELS_PER_FRAME != asbd->mChannelsPerFrame) | |
return false; | |
if(CDDA_BITS_PER_CHANNEL != asbd->mBitsPerChannel) | |
return false; | |
return true; | |
} | |
// ======================================== | |
// Generate the AccurateRip CRC for a sector of CDDA audio | |
// ======================================== | |
uint32_t | |
calculateAccurateRipChecksumForBlock(const void *block, uint32_t blockNumber, uint32_t totalBlocks, bool isFirstTrack, bool isLastTrack) | |
{ | |
// NSCParameterAssert(NULL != block); | |
if(isFirstTrack && 4 > blockNumber) | |
return 0; | |
else if(isLastTrack && 6 > (totalBlocks - blockNumber)) { | |
printf("skipping block %i\n", blockNumber); | |
return 0; | |
} | |
else if(isFirstTrack && 4 == blockNumber) { | |
const uint32_t *buffer = (const uint32_t *)block; | |
uint32_t sample = OSSwapHostToLittleInt32(buffer[AUDIO_FRAMES_PER_CDDA_SECTOR - 1]); | |
return AUDIO_FRAMES_PER_CDDA_SECTOR * (4 + 1) * sample; | |
} | |
else { | |
const uint32_t *buffer = (const uint32_t *)block; | |
uint32_t checksum = 0; | |
uint32_t blockOffset = AUDIO_FRAMES_PER_CDDA_SECTOR * blockNumber; | |
for(uint32_t i = 0; i < AUDIO_FRAMES_PER_CDDA_SECTOR; ++i) | |
checksum += OSSwapHostToLittleInt32(*buffer++) * ++blockOffset; | |
return checksum; | |
} | |
} | |
// ======================================== | |
// Calculate the AccurateRip checksum for the file at path | |
// ======================================== | |
uint32_t | |
calculateAccurateRipChecksumForFile(CFURLRef fileURL, bool isFirstTrack, bool isLastTrack) | |
{ | |
// NSCParameterAssert(nil != fileURL); | |
uint32_t checksum = 0; | |
// Open the file for reading | |
AudioFileID file = NULL; | |
OSStatus status = AudioFileOpenURL(fileURL, fsRdPerm, kAudioFileWAVEType, &file); | |
if(noErr != status) | |
return 0; | |
// Verify the file contains CDDA audio | |
AudioStreamBasicDescription fileFormat; | |
UInt32 dataSize = sizeof(fileFormat); | |
status = AudioFileGetProperty(file, kAudioFilePropertyDataFormat, &dataSize, &fileFormat); | |
if(noErr != status) | |
goto cleanup; | |
if(!streamDescriptionIsCDDA(&fileFormat)) | |
goto cleanup; | |
// Determine the total number of audio packets (frames) in the file | |
UInt64 totalPackets; | |
dataSize = sizeof(totalPackets); | |
status = AudioFileGetProperty(file, kAudioFilePropertyAudioDataPacketCount, &dataSize, &totalPackets); | |
if(noErr != status) | |
goto cleanup; | |
// Convert the number of frames to the number of blocks (CDDA sectors) | |
uint32_t totalBlocks = (uint32_t)(totalPackets / AUDIO_FRAMES_PER_CDDA_SECTOR); | |
uint32_t blockNumber = 0; | |
// Set up extraction buffers | |
int8_t buffer [kCDSectorSizeCDDA]; | |
// Iteratively process each CDDA sector in the file | |
for(;;) { | |
UInt32 byteCount = kCDSectorSizeCDDA; | |
UInt32 packetCount = AUDIO_FRAMES_PER_CDDA_SECTOR; | |
SInt64 startingPacket = blockNumber * AUDIO_FRAMES_PER_CDDA_SECTOR; | |
status = AudioFileReadPackets(file, false, &byteCount, NULL, startingPacket, &packetCount, buffer); | |
if(noErr != status || kCDSectorSizeCDDA != byteCount || AUDIO_FRAMES_PER_CDDA_SECTOR != packetCount) | |
break; | |
checksum += calculateAccurateRipChecksumForBlock(buffer, blockNumber++, totalBlocks, isFirstTrack, isLastTrack); | |
} | |
cleanup: | |
/*status = */AudioFileClose(file); | |
return checksum; | |
} | |
/* | |
Special thanks to Gregory S. Chudov for the following: | |
AccurateRip CRC is linear. Here is what i mean by that: | |
Let's say we have a block of data somewhere in the middle of the track, starting from sample #N; | |
Let's calculate what does it contrubute to a track ArCRC, and call it S[0]: | |
S[0] = sum ((i + N)*sample[i]); | |
Now we want to calculate what does the same block of data contribute to the ArCRC offsetted by X, and call it S[X]: | |
S[X] = sum ((i + N + X)*sample[i]); | |
Obviously, S[X] = S[0] + X * sum (sample[i]); | |
So in fact, we only need to calculate two base sums: | |
SA = sum (i * sample[i]); | |
SB = sum (sample[i]); | |
Then we can calculate all offsetted CRCs easily: | |
S[0] = SA + N * SB; | |
... | |
S[X] = SA + (N+X) * SB; | |
So instead of double cycle (for each offset process each sample) you can have two consecutive cycles, first for samples, second for offsets. | |
You can calculate thousands of offsetted CRCs for the price of two. | |
The tricky part is when you process the samples which are close to the track boundaries. | |
For those samples you'll have to revert for the old algorithm. | |
*/ | |
// ======================================== | |
// Calculate the AccurateRip checksums for the file at path | |
// ======================================== | |
uint32_t * | |
calculateAccurateRipChecksumsForTrackInFile(CFURLRef fileURL, CFRange trackSectors, bool isFirstTrack, bool isLastTrack, uint32_t maximumOffsetInBlocks, bool assumeMissingSectorsAreSilence) | |
{ | |
// NSCParameterAssert(nil != fileURL); | |
// The number of audio frames in the track | |
uint32_t totalFramesInTrack = trackSectors.length * AUDIO_FRAMES_PER_CDDA_SECTOR; | |
// Checksums will be tracked in this array | |
uint32_t *checksums = NULL; | |
// Open the file for reading | |
AudioFileID file = NULL; | |
OSStatus status = AudioFileOpenURL((CFURLRef)fileURL, fsRdPerm, kAudioFileWAVEType, &file); | |
if(noErr != status) | |
return nil; | |
// Verify the file contains CDDA audio | |
AudioStreamBasicDescription fileFormat; | |
UInt32 dataSize = sizeof(fileFormat); | |
status = AudioFileGetProperty(file, kAudioFilePropertyDataFormat, &dataSize, &fileFormat); | |
if(noErr != status) | |
goto cleanup; | |
if(!streamDescriptionIsCDDA(&fileFormat)) | |
goto cleanup; | |
// Determine the total number of audio packets (frames) in the file | |
UInt64 totalPackets; | |
dataSize = sizeof(totalPackets); | |
status = AudioFileGetProperty(file, kAudioFilePropertyAudioDataPacketCount, &dataSize, &totalPackets); | |
if(noErr != status) | |
goto cleanup; | |
// Convert the number of frames to the number of blocks (CDDA sectors) | |
uint32_t totalBlocks = (uint32_t)(totalPackets / AUDIO_FRAMES_PER_CDDA_SECTOR); | |
// Determine if any sectors are missing at the beginning or end | |
uint32_t missingSectorsAtStart = 0; | |
uint32_t missingSectorsAtEnd = 0; | |
if(trackSectors.length + (2 * maximumOffsetInBlocks) > totalBlocks) { | |
uint32_t missingSectors = trackSectors.length + (2 * maximumOffsetInBlocks) - totalBlocks; | |
if(maximumOffsetInBlocks > trackSectors.location) | |
missingSectorsAtStart = maximumOffsetInBlocks - trackSectors.location; | |
missingSectorsAtEnd = missingSectors - missingSectorsAtStart; | |
} | |
// If there aren't enough sectors (blocks) in the file, it can't be processed | |
if(totalBlocks < trackSectors.length) | |
goto cleanup; | |
// Missing non-track sectors may be allowed | |
if(!assumeMissingSectorsAreSilence && (missingSectorsAtStart || missingSectorsAtEnd)) | |
goto cleanup; | |
uint32_t maximumOffsetInFrames = maximumOffsetInBlocks * AUDIO_FRAMES_PER_CDDA_SECTOR; | |
// The inclusive range of blocks making up the track | |
uint32_t firstFileBlockForTrack = trackSectors.location; | |
uint32_t lastFileBlockForTrack = firstFileBlockForTrack + trackSectors.length - 1; | |
// Only blocks in the middle of the track can be processed using the fast offset calculation algorithm | |
uint32_t firstFileBlockForFastProcessing; | |
if(isFirstTrack) | |
firstFileBlockForFastProcessing = firstFileBlockForTrack + (5 > maximumOffsetInBlocks ? 5 : maximumOffsetInBlocks); | |
else | |
firstFileBlockForFastProcessing = firstFileBlockForTrack + maximumOffsetInBlocks; | |
uint32_t lastFileBlockForFastProcessing; | |
if(isLastTrack) | |
lastFileBlockForFastProcessing = lastFileBlockForTrack - (5 > maximumOffsetInBlocks ? 5 : maximumOffsetInBlocks); | |
else | |
lastFileBlockForFastProcessing = lastFileBlockForTrack - maximumOffsetInBlocks; | |
// Set up the checksum buffer | |
checksums = calloc((2 * maximumOffsetInFrames) + 1, sizeof(uint32_t)); | |
// The extraction buffer | |
int8_t buffer [kCDSectorSizeCDDA]; | |
// Iteratively process each CDDA sector of interest in the file | |
for(uint32_t fileBlockNumber = firstFileBlockForTrack - maximumOffsetInBlocks + missingSectorsAtStart; fileBlockNumber <= lastFileBlockForTrack + maximumOffsetInBlocks - missingSectorsAtEnd; ++fileBlockNumber) { | |
UInt32 byteCount = kCDSectorSizeCDDA; | |
UInt32 packetCount = AUDIO_FRAMES_PER_CDDA_SECTOR; | |
SInt64 startingPacket = fileBlockNumber * AUDIO_FRAMES_PER_CDDA_SECTOR; | |
status = AudioFileReadPackets(file, false, &byteCount, NULL, startingPacket, &packetCount, buffer); | |
if(noErr != status || kCDSectorSizeCDDA != byteCount || AUDIO_FRAMES_PER_CDDA_SECTOR != packetCount) | |
break; | |
int32_t trackBlockNumber = fileBlockNumber - firstFileBlockForTrack; | |
int32_t trackFrameNumber = trackBlockNumber * AUDIO_FRAMES_PER_CDDA_SECTOR; | |
const uint32_t *sampleBuffer = (const uint32_t *)buffer; | |
// Sectors in the middle of the track can be processed quickly | |
if(fileBlockNumber >= firstFileBlockForFastProcessing && fileBlockNumber <= lastFileBlockForFastProcessing) { | |
uint32_t sumOfSamples = 0; | |
uint32_t sumOfSamplesAndPositions = 0; | |
// Calculate two sums for the audio | |
for(uint32_t frameIndex = 0; frameIndex < AUDIO_FRAMES_PER_CDDA_SECTOR; ++frameIndex) { | |
uint32_t sample = OSSwapHostToLittleInt32(*sampleBuffer++); | |
sumOfSamples += sample; | |
sumOfSamplesAndPositions += sample * (frameIndex + 1); | |
} | |
for(int32_t offsetIndex = -maximumOffsetInFrames; offsetIndex <= (int32_t)maximumOffsetInFrames; ++offsetIndex) | |
checksums[offsetIndex + maximumOffsetInFrames] += sumOfSamplesAndPositions + ((trackFrameNumber - offsetIndex) * sumOfSamples); | |
} | |
// Sectors at the beginning or end of the track or disc must be handled specially | |
// This could be optimized but for now it uses the normal method of Accurate Rip checksum calculation | |
else { | |
for(uint32_t frameIndex = 0; frameIndex < AUDIO_FRAMES_PER_CDDA_SECTOR; ++frameIndex) { | |
uint32_t sample = OSSwapHostToLittleInt32(*sampleBuffer++); | |
for(int32_t offsetIndex = -maximumOffsetInFrames; offsetIndex <= (int32_t)maximumOffsetInFrames; ++offsetIndex) { | |
// Current frame is the track's frame number in the context of the current offset | |
int32_t currentFrame = trackFrameNumber + (int32_t)frameIndex - offsetIndex; | |
// The current frame is in the skipped area of the first track on the disc | |
if(isFirstTrack && ((5 * AUDIO_FRAMES_PER_CDDA_SECTOR) - 1) > currentFrame) | |
; | |
// The current frame is in the skipped area of the last track on the disc | |
else if(isLastTrack && (totalFramesInTrack - (5 * AUDIO_FRAMES_PER_CDDA_SECTOR)) <= currentFrame) | |
; | |
// The current frame is in the previous track | |
else if(0 > currentFrame) | |
; | |
// The current frame is in the next track | |
else if(currentFrame >= (int32_t)totalFramesInTrack) | |
; | |
// Process the sample | |
else | |
checksums[offsetIndex + maximumOffsetInFrames] += sample * (uint32_t)(currentFrame + 1); | |
} | |
} | |
} | |
} | |
cleanup: | |
/*status = */AudioFileClose(file); | |
return checksums; | |
} | |
int | |
main(int argc, const char * argv[]) | |
{ | |
if(2 > argc) { | |
printf("Usage: %s file1 file2 ...\n", argv[0]); | |
return 1; | |
} | |
while(0 < --argc) { | |
const char *path = argv[argc]; | |
CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)path, strlen(path), false); | |
if(!url) | |
continue; | |
uint32_t checksum1 = calculateAccurateRipChecksumForFile(url, true, false); | |
uint32_t checksum2 = calculateAccurateRipChecksumForFile(url, false, false); | |
uint32_t checksum3 = calculateAccurateRipChecksumForFile(url, false, true); | |
printf("%s: %.8x %.8x %.8x\n", path, checksum1, checksum2, checksum3); | |
uint32_t *checksums = calculateAccurateRipChecksumsForTrackInFile(url, CFRangeMake(0, 22123), false, true, 0, false); | |
printf("alt. checksum: %.8x",checksums[0]); | |
free(checksums); | |
CFRelease(url); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment