Subscribe to me on YouTube

Loading sound from memory into cocos2d’s SimpleAudioEngine

April 19th, 2012 by Cody

This was seriously way more work than I anticipated. In the end, it really wasn’t that complicated, but just took a while to put all the pieces together. Having to wrap the ExtAudioFileRef around an AudioFileID and then having to setup callback functions to get the AudioFileID from memory felt like a bit of overkill… Here is the code I added to CDOpenALSupport.m. I’m sure there is a better solution, but this works so I’m gonna stick with it for now ^_^.

//
// desc: for storing the decrypted audio file data in memory
//
typedef struct {
    unsigned char *data;    // pointer to audio data
    SInt64 dataLength;      // length of audio data
} AudioFileMemory;

//
// desc: callback for reading the audio data, 
//       check AudioFileOpenWithCallbacks doc for more details
//
OSStatus AudioFileReadProc(void *inClientData, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount)
{
    // check parameters
    if (!inClientData || !buffer || !actualCount) {
        return EINVAL;
    }
    
    AudioFileMemory *audioFileMemory = (AudioFileMemory *)inClientData;
        
    // make sure position is within bounds
    if (inPosition < 0 || inPosition >= audioFileMemory->dataLength) {
        *actualCount = 0; // don't read anything and tell them everything is just friggin fine,
                          // this is called passive aggressive error handling ^_^
        return noErr;
    }
    
    // see if we need to cap requested length
    *actualCount = requestCount;
    SInt64 endPosition = inPosition + requestCount;
    if (endPosition >= audioFileMemory->dataLength) {
        *actualCount = requestCount - (endPosition - audioFileMemory->dataLength);
    }
    
    memcpy(buffer, audioFileMemory->data + inPosition, *actualCount);
    return noErr;
}

//
// desc: callback for getting size of audio data 
//       check AudioFileOpenWithCallbacks doc for more details
//
SInt64 AudioFileGetSizeProc(void *inClientData) {
    if (!inClientData) {
        return EINVAL;
    }
    
    AudioFileMemory *audioFileMemory = (AudioFileMemory *)inClientData;
    return audioFileMemory->dataLength;
}

//
// desc: assumes this is encrypted or packaged file and that
//       the file naming convention is filename.xxx.enc or filename.xxx.tar, etc...
//       determines type based off xxx, only coded for checking for wave and mp3
//       cause that's all I care about right now
//
// params: inFileURL[in] - file url to get audio file type on
//
// returns: audio file type id for file if successful
//          returns 0 if file type is unknown
//
AudioFileTypeID GetAudioFileTypeId(CFURLRef inFileURL)
{
    CFURLRef pathWithoutEncExtension = CFURLCreateCopyDeletingPathExtension(kCFAllocatorDefault, inFileURL);
    CFStringRef extension = CFURLCopyPathExtension(pathWithoutEncExtension);
    AudioFileTypeID audioFileTypeId = 0;
    if (CFStringCompare(extension, (CFStringRef)@"wav", kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
        audioFileTypeId = kAudioFileWAVEType;
    }
    else if (CFStringCompare(extension, (CFStringRef)@"mp3", kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
        audioFileTypeId = kAudioFileMP3Type;
    }
    
    CFRelease(pathWithoutEncExtension);
    CFRelease(extension);
    return audioFileTypeId;
}

//
// desc: creates an audio file id, checks to see if file type is encrypted.
//       if it's not encrypted, then it loads it like normal and returns audioFileId.
//       if is its encrypted, it will decrypt the file in memory and load it into a AudioFileId.
//       if it decrypts, then it will return pointer to the audio data in audioFileMemory.  when
//       you close the audioFileId, be sure to free() the data pointer inside audioFileMemory.
//       I'm not really sure if we need to keep the data around, but I'm doing it to be safe.
//
// params: inFileURL[in] - url of file to load
//         audioFileId[out] - returns audio file id if file was successfully loaded
//         audioFileMemory[out] - returns pointer to audio memory if the audio file was decrpyted and loaded from mem
//                                BE SURE TO CALL free() ON THE DATA POINTER WHEN YOU ARE DONE WITH THE AUDIO FILE ID
//
// returns: returns  0 if opened file like normal (not encrypted)
//          returns  1 if dectypred file and loaded it from memory
//          returns -1 if failed to open unencrypted file
//          returns -2 if failed to open encrypted file
//          returns -3 if failed to decrypt file
//          returns -4 if failed to load audio file id with decrypted memory
//
int CreateAudioFileId(CFURLRef inFileURL, AudioFileID *audioFileId, AudioFileMemory *audioFileMemory)
{
    CFStringRef extension = CFURLCopyPathExtension(inFileURL);
    OSStatus err = noErr;
    
    // reset audioFileMemory
    memset(audioFileMemory, 0, sizeof(AudioFileMemory));
    
    // if not an encrypted file, then get audio id like always
    if (CFStringCompare(extension, (CFStringRef)@"enc", kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
        CFRelease(extension);
        
        err = AudioFileOpenURL(inFileURL, kAudioFileReadPermission, 0, audioFileId);
        if (err) {
            CDLOG(@"CreateAudioFileId: AudioFileOpenURL FAILED, Error = %ld\n", err);
            return -1;
        }
        
        return 0;
    }
    
    CFRelease(extension);
    
    // else we are dealing with encrypted file, so decrypt it
    @autoreleasepool {
        NSData *encData = [NSData dataWithContentsOfURL:(NSURL *)inFileURL];
        
        if (!encData) {
            CDLOG(@"CreateAudioFileId: Failed to load %@, could not find file.", CFURLGetString(inFileURL));
            return -2;
        }
        
        audioFileMemory->dataLength = CCDecryptMemory((unsigned char *)[encData bytes], [encData length], &audioFileMemory->data);
        if (!audioFileMemory->data) {
            CDLOG(@"CreateAudioFileId: Failed to load %@, failed to decrypt.", CFURLGetString(inFileURL));
            return -3;
        }
    }
    
    err = AudioFileOpenWithCallbacks(audioFileMemory,
                                     AudioFileReadProc, NULL,
                                     AudioFileGetSizeProc, NULL,
                                     GetAudioFileTypeId(inFileURL), audioFileId);
        
    if (err) {
        char bytes[4];
        bytes[0] = (err >> 24) & 0xFF;
        bytes[1] = (err >> 16) & 0xFF;
        bytes[2] = (err >> 8) & 0xFF;
        bytes[3] = err & 0xFF;
        CDLOG(@"CreateAudioFileId: AudioFileOpenWithCallbacks FAILED, Error = %ld, %c%c%c%c\n", err, bytes[0], bytes[1], bytes[2], bytes[3]);
        return -4;
    }
    
    return 1;
}



And after adding all that, just had to change CDloadWaveAudioData() and CDloadCafAudioData to use the new CreateAudioFileId() calls.

CDloadWaveAudioData() change:

//Taken from oalTouch MyOpenALSupport 1.1
void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei*	outSampleRate)
{
	OSStatus						err = noErr;	
	UInt64							fileDataSize = 0;
	AudioStreamBasicDescription		theFileFormat;
	UInt32							thePropertySize = sizeof(theFileFormat);
	AudioFileID						afid = 0;
	void*							theData = NULL;
    AudioFileMemory                 audioFileMemory;
	
	// Open a file with ExtAudioFileOpen()
    if (CreateAudioFileId(inFileURL, &afid, &audioFileMemory) < 0) {
        goto Exit;
    }

CDloadCafAudioData change:

//Taken from oalTouch MyOpenALSupport 1.4
void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate)
{
	OSStatus						status = noErr;
	BOOL							abort = NO;
	SInt64							theFileLengthInFrames = 0;
	AudioStreamBasicDescription		theFileFormat;
	UInt32							thePropertySize = sizeof(theFileFormat);
	ExtAudioFileRef					extRef = NULL;
	void*							theData = NULL;
	AudioStreamBasicDescription		theOutputFormat;
	UInt32							dataSize = 0;
    AudioFileID                     audioFileId = 0;
    AudioFileMemory                 audioFileMemory;
    
    // create audio file id
    if (CreateAudioFileId(inFileURL, &audioFileId, &audioFileMemory) < 0) {
        goto Exit;
    }
	
	// Open a file with ExtAudioFileOpen()
    status = ExtAudioFileWrapAudioFileID(audioFileId, false, &extRef);
	if (status != noErr)
	{
		CDLOG(@"MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", status);
		abort = YES;
	}
	if (abort)
		goto Exit;

5 Responses to “Loading sound from memory into cocos2d’s SimpleAudioEngine”

  1. [...] Nice solution for loading sound from memory into cocos2d’s SimpleAudioEngine – LINK [...]

  2. ahmad says:

    I got a error when implement your code. im using cocos2d 1.0.1.

    Implicit declaration of function 'CCDecryptMemory' is invalid in C99

    • PingPongRhino says:

      CCDecryptMemory() is my decryption function. You will want to implement your own encryption/decryption methods. I used sample code from CryptUtils.cpp which you can find in the project here. The link also has information on encrypting/decryption textures. My CCDecrypt() takes the encData as input, and outputs the decrypted data into the audioFileMemory->data and the size of the decrypted data into audioFileMemory->dataLength. Hope that helps.

  3. Kshitij says:

    Could you please share the source in the file?

    • PingPongRhino says:

      I decided to release code for the game under MIT License. I just stuck it up on git hub. You should be able to find all source here, https://github.com/PingPongRhino/sbat2. Note that I stripped all the images and sounds since I still wanted someone to still have to do a little work to steal my work. Hope it helps someone out there.

Leave a Reply