iosobjective-ccore-audioaudiotoolboxaudiobufferlist

Writing array of floats to audio file


I have an array of Float32 values called _recordingSamples that I wish to write to an audio file. The value of _recordingLength is about 390000. I'm using the following code:

NSString *outputPath = @"/Users/evanjackson/output.caf";
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager createFileAtPath:outputPath contents:nil attributes:nil];

AudioStreamBasicDescription _audioFormat;
_audioFormat.mSampleRate = 44100.0;
_audioFormat.mFormatID = kAudioFormatLinearPCM;
_audioFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
_audioFormat.mChannelsPerFrame = 1;
_audioFormat.mBitsPerChannel = 32;
_audioFormat.mBytesPerFrame = 4;
_audioFormat.mFramesPerPacket = 1;
_audioFormat.mBytesPerPacket = _audioFormat.mFramesPerPacket * _audioFormat.mBytesPerFrame;

UInt32 lengthPropertySize = sizeof(SInt64);
ExtAudioFileRef filteredAudio;
NSURL *destinationURL = [NSURL URLWithString:outputPath];
OSStatus status = ExtAudioFileCreateWithURL((__bridge CFURLRef)destinationURL, kAudioFileCAFType, &_audioFormat, NULL, kAudioFileFlags_EraseFile, &filteredAudio);
printf("Status: %d\n", status);
//ExtAudioFileOpenURL((__bridge CFURLRef)[NSURL fileURLWithPath:outputPath], &filteredAudio);
const AudioStreamBasicDescription audioFormat = [del audioFormat];
status = ExtAudioFileSetProperty(filteredAudio, kExtAudioFileProperty_ClientDataFormat, sizeof(audioFormat), &audioFormat);
printf("Status: %d\n", status);
status = ExtAudioFileGetProperty(filteredAudio, kExtAudioFileProperty_FileLengthFrames, &lengthPropertySize, &_recordingLength);
printf("Status: %d\n", status);
AudioBufferList *buffers = (AudioBufferList *)malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer));
buffers->mNumberBuffers = 1;
for (int i = 0; i < buffers->mNumberBuffers; i++) {
    buffers->mBuffers[0].mData = _recordingSamples;
    buffers->mBuffers[0].mNumberChannels = 1;
    buffers->mBuffers[0].mDataByteSize = _recordingLength * sizeof(Float32);
}

status = ExtAudioFileWrite(filteredAudio, _recordingLength, buffers);
printf("Status: %d\n", status);

All of the statuses return as 0, the file gets created (4 bytes) but Quicktime will not open it and returns the error -12842, and the compiler delivers the following message:

libc++abi.dylib: terminating with uncaught exception of type std::bad_alloc: std::bad_alloc
(lldb) 

Anybody know what's going wrong? Thanks in advance.


Solution

  • Scrap the code that I posted in the question. I found the solution here: How to write array of float values to audio file in Core Audio?. Look at the first answer. I went to the given link, downloaded the source code, then added EAFWrite.h and EAFWrite.mm to my project. The class assumes that the audio will be read from multiple buffers (ie: a 2D array), but needed it to work with a 1D array, so I modified the function writeToFloats as follows:

    -(OSStatus) writeFloats:(long)numFrames fromArray:(float *)data
    {
        OSStatus    err = noErr;
    
        if (!data)      return -1;
        if (!numFrames) return -1;
    
        AudioBufferList *abl = AllocateAudioBufferList(mStreamFormat.mChannelsPerFrame, numFrames*sizeof(short));
        if (!abl)       return -1;
    
        abl->mBuffers[0].mNumberChannels = 1;
        abl->mBuffers[0].mDataByteSize = numFrames*sizeof(short);
        short *buffer = (short*)abl->mBuffers[0].mData;
        for (long v = 0; v < numFrames; v++) {
            if (data[v] > 0.999)
                data[v] = 0.999;
            else if (data[v] < -1)
                data[v] = -1;
            buffer[v] = (short)(data[v]*32768.f);
        }
    
        abl->mBuffers[0].mData = buffer;
    
        err = ExtAudioFileWrite(mOutputAudioFile, numFrames, abl);
    
        DestroyAudioBufferList(abl);
    
        if(err != noErr)
        {
            char formatID[5];
            *(UInt32 *)formatID = CFSwapInt32HostToBig(err);
            formatID[4] = '\0';
            fprintf(stderr, "ExtAudioFileWrite FAILED! %d '%-4.4s'\n",(int)err, formatID);
            return err;
        }
    
        return err;
    
    }
    

    And to call this function you need:

    NSString *outputPath = @"outputFile.caf"
    NSFileManager *fileManager = [NSFileManager defaultManager];
    [fileManager createFileAtPath:outputPath contents:nil attributes:nil];
    NSURL* fileURL = [NSURL URLWithString:outputPath];
    
    EAFWrite *writer = [[EAFWrite alloc] init];
    [writer openFileForWrite:fileURL sr:44100.0 channels:1 wordLength:32 type:kAudioFileCAFType];
    [writer writeFloats:_recordingLength fromArray:_recordingSamples];