main.c

#include <glob.h>
#include "dru_devices.h"
#include "dru_burning.h"
#import <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
 
typedef struct AIFFFileInfo
{
    int             fd;             // The file descriptor of the open file
    unsigned        numChannels;    // The number of channels of audio 
    unsigned        sampleFreq;     // the sampling frequency. We're only going to support 44.1KHz.
    unsigned        sampleBits;     // bits/sample
    unsigned        sampleBytes;    // number of bytes for each sample. Calculated value
    unsigned long   dataStart;      // Where the sound data starts in the file
    unsigned long   dataEnd;        // Where the sound data ends in the file.
    unsigned long   cursor;         // where in the file we're at
    int             mask;           // channel mixing mask
 
} AIFFFileInfo;
 
 
int main(int argc, char *argv[]);
 
/* Creating the audio tracks */
CFArrayRef       createTracks();
CFMutableDictionaryRef 
                createAudioTrackProperties(uint32_t trackLength);
 
/* track production callback functions */
OSStatus        sampleTrackProducerCallback(DRTrackRef track, DRTrackMessage message, void* ioParam);
OSStatus        preBurnCallback(DRTrackRef track);
OSStatus        productionCallback(DRTrackRef track, DRTrackProductionInfo* prodInfo);
OSStatus        postBurnCallback(DRTrackRef track);
 
/* utility routines */
void            expandPathname(char *path);
AIFFFileInfo*   parseFile(char* filepath);
char*           promptForFolder(const char *prompt, const char *defaultPath, char* folderPath, int folderPathLen);
void            trackRefConDestructor(const void* refCon);
 
 
#pragma mark -
 
 
int main(int argc, char *argv[])
{
#pragma unused(argc, argv)
    DRDeviceRef             device = NULL;
    DRBurnRef               burn = NULL;
    CFArrayRef              trackLayout = NULL;
    
    /* Hello world! */
    printf("Welcome to the audio burn sample code!\n\n");
    
    /* First, let's use DRU to prompt the user to 
       pick a device. If there's only one device to 
       choose, the selection is automatic. */
    device = druPromptForDevice(NULL,druFilter_AnyBurner);
    
    /* We know we're going to burn to this device - acquire blank media
        reservations just in case the user inserts something. 
        Just because we ask for a shot at blank media doesn't mean that
        we'll get it. Some other app might not be willing to give 
        up the blank media for our use. */
    DRDeviceAcquireMediaReservation(device);
        
    /* create the AIFF tracks we'll be burning to CD. */
    trackLayout = createTracks();
    
    /* Next, use DRU to prompt the user to insert blank media. */
    druPromptForBlankMediaInDevice(device);
    
    /* Check to see if we have the media reservation. If we don't 
        have it that means that some other application has it and 
        won't give it up. We might not get it if, for instance, if
        the Finder has claimed the media for it's CD proxy and the user
        has placed files onto that proxy image. In this case the 
        Finder wouldn't want to unmount the proxy image since the
        user's intention is to burn that information to the inserted
        disc. */
    if (druMediaIsReserved(device))
    {
        /* We've got blank media, and a folder to burn ... 
        now set up the burn objects. */
        burn = DRBurnCreate(device);
        
        /* Use DRU to run the burn and report progress. */
        druBurn(burn,trackLayout);  
    }
    else
    {
        printf("The media in the selected device is reserved for use by another application.\n");
    }
    
    /* Clean up after ourselves. */
    if (burn != NULL)
        CFRelease(burn);
        
    if (trackLayout != NULL)
        CFRelease(trackLayout);
        
    if (device != NULL)
    {
        DRDeviceReleaseMediaReservation(device);
        CFRelease(device);
    }
    return 0;
}
 
 
 
 
 
CFArrayRef createTracks()
{
    CFMutableArrayRef       trackArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
    char                    path[PATH_MAX];
    int                     pathLen;
    
    /* Prompt the user to pick a folder of AIFF's to burn. */
    promptForFolder(NULL,"~/Music", path, PATH_MAX);
    pathLen = strlen(path);
    
    // Remove trailing slashes, if any.
    if (pathLen>0 && path[pathLen-1] == '/') pathLen--;
    
    // Open the directory.
    DIR *dirfd = opendir((const char*)path);
    if (dirfd == NULL) return 0;
    
    while (1)
    {
        CFMutableStringRef  fileStr;
        CFStringRef         aStr;
        AIFFFileInfo*       info;
        char                filepath[PATH_MAX];
        DRRefConCallbacks   callbacks;
        CFMutableDictionaryRef  properties;
        
        /* Get the next child.  This is threadsafe, because the struct dirent* is
           not global; it's actually pointing to a buffer that was malloc'ed
           inside the DIR structure. */
        struct dirent *childPtr = readdir(dirfd);
        if (childPtr == NULL) break;
        int nameLength = childPtr->d_namlen;
        char *name = childPtr->d_name;
        
        if (childPtr->d_type == DT_DIR)
            continue;
            
        /* Skip "." and ".." entries, and AppleDouble "._" entries which 
           we treat as one file. */
        if (name[0] == '.' && (nameLength == 1 || name[1] == '_' || (name[1] == '.' && nameLength == 2)))
            continue;
        
        /* skip all non-AIFF files by looking at the extension. */
        if (strncmp(".aiff", &name[nameLength-5], 5) != 0 && strncmp(".aif", &name[nameLength-4], 4) != 0)
            continue;
        
        aStr = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
        fileStr = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, aStr);
        CFRelease(aStr);
        CFStringAppendCString(fileStr, "/", kCFStringEncodingUTF8);
        CFStringAppendCString(fileStr, name, kCFStringEncodingUTF8);
            
        /* parse out the file information and construct an information structure 
           to hold this info for use when we start the burn and start producing data. */
        CFStringGetCString(fileStr, filepath, PATH_MAX, kCFStringEncodingUTF8);
        info = parseFile(filepath);
        
        /* Create the properties dictionary describing this track. */
        properties = createAudioTrackProperties(((info->dataEnd - info->dataStart) / (info->sampleBytes * info->numChannels) * 4) / 2352);
        
        /* Add the file path for this file to the track properties. This will 
           be used by the producer callback later. */
        CFDictionarySetValue(properties, CFSTR("kTrackSourceFilePath"), fileStr);
        CFRelease(fileStr);
        
        /* Create a track that is described by properties and whose data is 
           produced by sampleTrackProducerCallback */
        DRTrackRef track = DRTrackCreate(properties, sampleTrackProducerCallback);
        
        /* stuff the info structure pointer into the refCon of the track object. Each object in
           DiscRecording has a refCon associated with it. */
        callbacks.version = 0;
        callbacks.retain = NULL;
        callbacks.release = trackRefConDestructor;  /* we just need to destroy it when the track is released */
        DRSetRefCon(track, info, &callbacks);
        
        /* Put the track into the array for burning */
        CFArrayAppendValue(trackArray, track);
        CFRelease(properties);
        CFRelease(track);
    }
    
    closedir(dirfd);
    
    
    if (CFArrayGetCount(trackArray) == 0)
    {
        printf("No AIFF files were located!  Aborting.\n");
        exit(1);
    }
    
    return trackArray;
}
 
 
 
 
 
CFMutableDictionaryRef createAudioTrackProperties(uint32_t trackLength)
{
    CFMutableDictionaryRef  properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFNumberRef             value;
    uint32_t                temp;
    
    /* Create a properties dictionary for all of the tracks. This dictionary
       is common to each since each will be an audio track and other than the size
       will be identical. */    
    temp = kDRBlockSizeAudio;
    value = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &temp);
    CFDictionaryAddValue(properties, kDRBlockSizeKey, value);
    CFRelease(value);
 
    temp = kDRBlockTypeAudio;
    value = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &temp);
    CFDictionaryAddValue(properties, kDRBlockTypeKey, value);
    CFRelease(value);
 
    temp = kDRDataFormAudio;
    value = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &temp);
    CFDictionaryAddValue(properties, kDRDataFormKey, value);
    CFRelease(value);
 
    temp = kDRSessionFormatAudio;
    value = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &temp);
    CFDictionaryAddValue(properties, kDRSessionFormatKey, value);
    CFRelease(value);
 
    temp = kDRTrackModeAudio;
    value = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &temp);
    CFDictionaryAddValue(properties, kDRTrackModeKey, value);
    CFRelease(value);
 
    value = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &trackLength);
    CFDictionarySetValue(properties, kDRTrackLengthKey, value);
    CFRelease(value);
    
    return properties;
}
 
 
 
 
 
/* This is a dispatcher to take the message and callout to different 
   functions to handle the messages we handle. */
OSStatus sampleTrackProducerCallback(DRTrackRef track, DRTrackMessage message, void* ioParam)
{
    switch (message)
    {
        case kDRTrackMessagePreBurn:
            return preBurnCallback(track);
            break;
            
        case kDRTrackMessageProduceData:
            return productionCallback(track, (DRTrackProductionInfo*)ioParam);
            break;
        
        case kDRTrackMessagePostBurn:
            return postBurnCallback(track);
            break;
    }
    
    return noErr;
}
 
 
 
 
 
/* This callback is called before the burn is to begin. It it we open up the AIFF file to read
   from and make sure it's valid (this could have been done before we started also). We also
   set up the final length of the track */
OSStatus preBurnCallback(DRTrackRef track)
{
    CFDictionaryRef properties = DRTrackGetProperties(track);
    CFStringRef     fileStr = CFDictionaryGetValue(properties, CFSTR("kTrackSourceFilePath"));
    char            filepath[PATH_MAX];
    AIFFFileInfo*   info = (AIFFFileInfo*)DRGetRefCon(track);
 
    /* Get the file string from the properties dictionary and get the c string from that 
       to open the file using open */
    CFStringGetCString(fileStr, filepath, PATH_MAX, kCFStringEncodingUTF8);
    
    info->fd = open(filepath, O_RDONLY, 0);
    if (info->fd == -1) return kDRDataProductionErr;
 
    fcntl(info->fd, F_NOCACHE, 1);                      /* Don't cache the file in the UBC */
    lseek(info->fd, info->dataStart, SEEK_SET);         /* put the file pointer at the start of the sound data */
    info->mask = ((int)-0x10000) >> info->sampleBits;   /* set up the mask to do channel mixing */
    info->cursor = info->dataStart;
    
    return noErr;
}
 
 
 
 
 
/* The meat of the producer code. This callback is called repeatedly during a burn.
    It's this function's job to write data into the passed in buffer each time it's 
    called.  It's best if you can fill up the buffer completely. The buffer is
    a multiple of the track's block size in length. */
OSStatus productionCallback(DRTrackRef track, DRTrackProductionInfo* prodInfo)
{
    unsigned long       expectedFrames = (prodInfo->reqCount / 4);
    unsigned long       readSize, readLen;
    char*               tempBuffer;
    char*               outBuffer = prodInfo->buffer;
    unsigned long       outLength;
    unsigned long       i;
    unsigned long       step;
    AIFFFileInfo*       info = (AIFFFileInfo*)DRGetRefCon(track);
 
    readSize = (info->sampleBytes * info->numChannels) * expectedFrames;
    step = info->sampleBytes*info->numChannels;
 
    if (readSize + info->cursor > info->dataEnd)
        readSize = info->dataEnd - info->cursor;
 
    tempBuffer = (char*)malloc(readSize);
    
    readLen = read(info->fd, tempBuffer, readSize);
    if (readSize != readLen) return kDRDataProductionErr;
 
    outLength = 0;
    for (i=0; i<readSize; i += step)
    {
        unsigned short          leftSample = 0, rightSample = 0;
        unsigned const char*    sampleFrame = tempBuffer + i;
        
        #define READ_SAMPLE_POINT(frame,index)      \
            ((*(uint16_t*)(frame + info->sampleBytes*index)) & info->mask)
        #define MIX(a,b)                            \
            (((a * 2) / 3) + ((b * 2) / 3))
        
        /* Handle our wonderful multichannel logic.  Yeah, it's almost certainly unnecessary,
           but it took an extra 2 minutes to write it this way since I was already thinking
           about how to parse the AIFF properly.  Maybe it'll make someone's world a better place. */
        if (info->numChannels == 2)
        {
            leftSample = READ_SAMPLE_POINT(sampleFrame,0);
            rightSample = READ_SAMPLE_POINT(sampleFrame,1);
        }
        else if (info->numChannels == 1)
        {
            leftSample = READ_SAMPLE_POINT(sampleFrame,0);
            rightSample = leftSample;
        }
        else if (info->numChannels == 3)
        {
            unsigned short  centerSample = READ_SAMPLE_POINT(sampleFrame,2);
            leftSample = MIX(READ_SAMPLE_POINT(sampleFrame,0),centerSample);
            rightSample = MIX(READ_SAMPLE_POINT(sampleFrame,1),centerSample);
        }
        else if (info->numChannels == 4)
        {
            /* The spec is unclear on how to distinguish quadrophonic vs 4 ch surround AIFFs, as
               they both have four channels.  Since surround seems to be the winner of these
               ancient audio wars, for now I'm going to assume 4 channels means surround.
               unsigned short that it matters. */
            UInt16  centerSample = MIX(READ_SAMPLE_POINT(sampleFrame,1),READ_SAMPLE_POINT(sampleFrame,3));
            leftSample = MIX(READ_SAMPLE_POINT(sampleFrame,0),centerSample);
            rightSample = MIX(READ_SAMPLE_POINT(sampleFrame,2),centerSample);
        }
        else if (info->numChannels == 6)
        {
            unsigned short  centerSample = MIX(READ_SAMPLE_POINT(sampleFrame,2),READ_SAMPLE_POINT(sampleFrame,5));
            leftSample = MIX(MIX(READ_SAMPLE_POINT(sampleFrame,0),READ_SAMPLE_POINT(sampleFrame,1)),centerSample);
            rightSample = MIX(MIX(READ_SAMPLE_POINT(sampleFrame,3),READ_SAMPLE_POINT(sampleFrame,4)),centerSample);
        }
        else
            return kDRDataProductionErr;
            
        #undef READ_SAMPLE_POINT
        #undef MIX
        
        // Dump the samples into the output.
        ((unsigned short*)outBuffer)[0] = ((leftSample & 0xFF00) >> 8) + ((leftSample & 0x00FF) << 8);
        ((unsigned short*)outBuffer)[1] = ((rightSample & 0xFF00) >> 8) + ((rightSample & 0x00FF) << 8);
        outBuffer += 4;
        outLength += 4;
    }
    
    free(tempBuffer);
    
    info->cursor += outLength;
    prodInfo->actCount = outLength;
    return noErr;
}
 
 
 
 
 
/* This callback comes in after the burn is finished and all data has been written
    to disc and optionally verified. All we need to do is to close the file opened in
    preBurn. */
OSStatus postBurnCallback(DRTrackRef track)
{
    AIFFFileInfo*       info = (AIFFFileInfo*)DRGetRefCon(track);
 
    close(info->fd);
    info->fd = 0;
    
    return noErr;
}
 
 
 
 
 
void trackRefConDestructor(const void* refCon)
{
    AIFFFileInfo*   info = (AIFFFileInfo*)refCon;
    if (info->fd != 0)
    {
        close(info->fd);
        info->fd = 0;
    }
    free(info);
}
 
 
 
 
 
char* promptForFolder(const char *prompt, const char *defaultPath, char* folderPath, int folderPathLen)
{
    char        path[PATH_MAX];
    OSStatus    err;
    Boolean     isDirectory;
    int         len;
    FSRef       ref;
    
    /* Display the prompt. */
    if (prompt == NULL)
        prompt = "Please enter the path to a folder:";
    printf("%s ", prompt);
    if (defaultPath != NULL)
        printf("[%s] ",defaultPath);
    fflush(stdout);
    
    /* Get user input, and trim trailing newlines. */
    fgets(path,sizeof(path),stdin);
    for (len = strlen(path); len > 0 && path[len-1] == '\n';)
        path[--len] = 0;
    if (path[0] == 0)
        strcpy(path,defaultPath);
    
    /* Expand magic characters just like a shell (mostly so ~ will work) */
    expandPathname(path);
    
    /* Convert the path into an FSRef, which is what the burn engine needs. */
    err = FSPathMakeRef((const UInt8*)path,&ref,&isDirectory);
    if (err != noErr)
    {
        printf("Bad path. Aborting. (%d)\n", (int)err);
        exit(1);
    }
    if (!isDirectory)
    {
        printf("That's a file, not a directory!  Aborting.\n");
        exit(1);
    }
    
    return strncpy(folderPath, path, folderPathLen);
}
 
 
 
 
 
void expandPathname(char *path)
{
    char    original[PATH_MAX];
    int     i;
    glob_t  g = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    
    /* Preserve the incoming path */
    strcpy(original,path);
    
    /* Call glob to expand it. */
    glob(path,GLOB_NOSORT | GLOB_TILDE | GLOB_QUOTE,NULL,&g);
    if (g.gl_matchc == 1)
        strcpy(path,g.gl_pathv[0]);
    else
    {
        printf("Bad path! ");
        if (g.gl_matchc == 0)
            printf("Not found. ");
        else
        {
            printf("%d matches found:\n",g.gl_matchc);
            for (i=0; i<g.gl_matchc; ++i)
                printf(" %s\n", g.gl_pathv[i]);
        }
        printf("Aborted.\n");
        exit(1);
    }
    globfree(&g);
}
 
 
 
 
 
#if PRAGMA_STRUCT_ALIGN
    #pragma options align=mac68k
#elif PRAGMA_STRUCT_PACKPUSH
    #pragma pack(push, 2)
#elif PRAGMA_STRUCT_PACK
    #pragma pack(2)
#endif
 
typedef struct IFFChunkHeader   IFFChunkHeader;
typedef struct AIFFHeader AIFFHeader;
typedef struct AIFFChunk AIFFChunk;
typedef struct AIFFCommonChunk AIFFCommonChunk;
typedef struct AIFFSoundChunk AIFFSoundChunk;
 
struct IFFChunkHeader
{
    UInt32          chunkID;        // id for this chunk
    UInt32          chunkSize;      // size of this chunk not including header
};
 
 
struct AIFFChunk
{
    UInt32          chunkID;        // id for this chunk
    UInt32          chunkSize;      // size of this chunk not including header
    UInt32          data[1];        // streamed data
};
 
struct AIFFHeader
{
    UInt32          form;           // 'FORM'
    UInt32          formChunkSize;  // largely ignored, but it should be (filesize - 8)
    UInt32          aiff;           // 'AIFF'
    AIFFChunk       chunk[1];       // packed array of chunks
};
 
struct AIFFCommonChunk
{
    UInt32          comm;               // 'COMM'
    UInt32          commChunkSize;      // size of this chunk not including header (18 bytes)
    UInt16          numChannels;        // number of sound channels
    UInt32          numSampleFrames;    // number of sample frames - probably incorrect!
    UInt16          sampleSize;         // sample size in bits (eg, 16)
    UInt32          sampleFreq;         // sample frequency
    UInt8           zeroes[6];          // AIFF defines the preceding as an 80-bit IEEE 754 fp number... most folks seem to use only 4 bytes
};
 
struct AIFFSoundChunk
{
    UInt32          ssnd;               // 'SSND'
    UInt32          ssndChunkSize;      // size of this chunk not including header (8 bytes + samples)
    UInt32          offset;             // used for aligning, usually zero
    UInt32          blockSize;          // used for aligning, usually zero
};
 
const UInt32 AIFF_SAMPLE_RATE_44_1      = 0x400EAC44;
const UInt32 AIFF_SAMPLE_RATE_44_0      = 0x400DAC44;
 
#if PRAGMA_STRUCT_ALIGN
    #pragma options align=reset
#elif PRAGMA_STRUCT_PACKPUSH
    #pragma pack(pop)
#elif PRAGMA_STRUCT_PACK
    #pragma pack()
#endif
 
 
 
 
AIFFFileInfo* parseFile(char* filepath)
{
    AIFFFileInfo*       info = (AIFFFileInfo*)malloc(sizeof(AIFFFileInfo));
    AIFFHeader          aiffHeader;
    AIFFCommonChunk     commonChunk;
    AIFFSoundChunk      soundChunk;
    IFFChunkHeader      chunkHeader;
    ssize_t             readLen;
    off_t               filepos;
    
    /* Initialize our file info to reasonable values. */
    info->numChannels = 0;
    info->sampleFreq = 0;
    info->sampleBits = 0;
    info->sampleBytes = 0;
    info->dataStart = 0;
    info->dataEnd = 0;
    info->cursor = 0;
    info->mask = 0;
    
    /* Get a c string of the file path so we can open up the file */
    info->fd = open(filepath, O_RDONLY, 0);
    if (info->fd == -1)
    {
        printf("file '%s' could not be opened. Aborting\n", filepath);
        exit(1);
    }
    /* Read in what should be the aiff file FORM header. Check for valid values. */
    readLen = read(info->fd, &aiffHeader, sizeof(aiffHeader));
    if (sizeof(aiffHeader) != readLen)
    {
        printf("file '%s' is an invalid AIFF. Aborting\n", filepath);
        exit(1);
    }
    
    if (aiffHeader.form != 'FORM' || aiffHeader.aiff != 'AIFF')
    {
        printf("file '%s' is an invalid AIFF. Aborting\n", filepath);
        exit(1);
    }
 
 
    /* OK, it's a valid AIFF file, search through for the common chunk. We do this
       by reading in the "header" (my terminology) of a chunk. This will give us the 
       chunkID and the size of the chunk. If it's not the chunk we want, we'll skip over
       it to the next one. If the file's corrupted, we'll eventually hit EOF and 
       fall out. */ 
    filepos = lseek(info->fd, 12, SEEK_SET);
    chunkHeader.chunkSize = 0;
    do
    {
        filepos = lseek(info->fd, 0, SEEK_CUR);
        filepos = lseek(info->fd, filepos + chunkHeader.chunkSize, SEEK_SET);
        readLen = read(info->fd, &chunkHeader, sizeof(chunkHeader));
        if (sizeof(chunkHeader) != readLen)
        {
            printf("file '%s' is an invalid AIFF. Aborting\n", filepath);
            exit(1);
        }
    }
    while (chunkHeader.chunkID != 'COMM');
 
 
    filepos = lseek(info->fd, 0, SEEK_CUR);
    filepos = lseek(info->fd, filepos-sizeof(chunkHeader), SEEK_SET);
    readLen = read(info->fd, &commonChunk, sizeof(commonChunk));
    if (sizeof(commonChunk) != readLen)
    {
        printf("file '%s' is an invalid AIFF. Aborting\n", filepath);
        exit(1);
    }
    
    if (commonChunk.numSampleFrames == 0) {
        return noErr;       /* It's valid, just nothing we can really read from. We'll get a zero length track if we try. */
    }
 
    info->numChannels = commonChunk.numChannels;
    info->sampleFreq = commonChunk.sampleFreq;
    info->sampleBits = commonChunk.sampleSize;
    info->sampleBytes = (info->sampleBits + 7) / 8;
 
    if ((info->numChannels == 0) || (info->numChannels == 5) || (info->numChannels > 6))
    {
        printf("file '%s' is an invalid AIFF - wrong number of channels. Aborting\n", filepath);
        exit(1);
    }
 
    if (info->sampleFreq != AIFF_SAMPLE_RATE_44_1 && info->sampleFreq != AIFF_SAMPLE_RATE_44_0)
    {
        printf("file '%s' is an invalid AIFF - wrong sample rate. Aborting\n", filepath);
        exit(1);
    }
 
    if ((info->sampleBits < 1) || (info->sampleBits > 128))
    {
        printf("file '%s' is an invalid AIFF - sample bits are wrong. Aborting\n", filepath);
        exit(1);
    }
 
 
    /* go back the start of the file (remember chunks in an IFF file don't have to be in any order)
       and look for the Sound chunk the same way we did the Common chunk. */
    filepos = lseek(info->fd, 12, SEEK_SET);
    chunkHeader.chunkSize = 0;
    do
    {
        filepos = lseek(info->fd, 0, SEEK_CUR);
        filepos = lseek(info->fd, filepos + chunkHeader.chunkSize, SEEK_SET);
        readLen = read(info->fd, &chunkHeader, sizeof(chunkHeader));
        if (sizeof(chunkHeader) != readLen)
        {
            printf("file '%s' is an invalid AIFF. Aborting\n", filepath);
            exit(1);
        }
    }
    while (chunkHeader.chunkID != 'SSND');
 
    filepos = lseek(info->fd, 0, SEEK_CUR);
    filepos = lseek(info->fd, filepos-sizeof(chunkHeader), SEEK_SET);
    readLen = read(info->fd, &soundChunk, sizeof(soundChunk));
    if (sizeof(soundChunk) != readLen)
    {
        printf("file '%s' is an invalid AIFF. Aborting\n", filepath);
        exit(1);
    }
 
    info->dataStart = lseek(info->fd, 0, SEEK_CUR) + soundChunk.offset;
    info->dataEnd = lseek(info->fd, 0, SEEK_CUR) + soundChunk.ssndChunkSize - 8;
    if ((soundChunk.ssndChunkSize < 8) || (info->dataEnd > lseek(info->fd, 0, SEEK_END)))
        info->dataEnd = lseek(info->fd, 0, SEEK_END);
        
    close(info->fd);
    
    return info;
}