Skip to content

Instantly share code, notes, and snippets.

@sbooth
Created March 13, 2010 21:23
Show Gist options
  • Save sbooth/331559 to your computer and use it in GitHub Desktop.
Save sbooth/331559 to your computer and use it in GitHub Desktop.
Accurate Rip CRC utility
#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