1

I need to be able to programmatically read a WAV (or CAF) file and extract just the sample (audio) data as a byte array. What's the easiest/quickest way to do this?

MusiGenesis
  • 74,184
  • 40
  • 190
  • 334

2 Answers2

3

Assuming you're on iOS or OS X, you want the AudioToolbox framework, specifically the APIs in AudioFile.h (or ExtAudioFile.h if you need to convert the audio data to another format on read.)

e.g.,

#include <AudioToolbox/AudioFile.h>

...

AudioFileID audioFile;
OSStatus err = AudioFileOpenURL(fileURL, kAudioFileReadPermission, 0, &audioFile);
// get the number of audio data bytes
UInt64 numBytes = 0;
UInt32 dataSize = sizeof(numBytes);
err = AudioFileGetProperty(audioFile, kAudioFilePropertyAudioDataByteCount, &dataSize, &numBytes);

unsigned char *audioBuffer = (unsigned char *)malloc(numBytes);

UInt32 toRead = numBytes;
UInt64 offset = 0;
unsigned char *pBuffer = audioBuffer;
while(true) {
    err = AudioFileReadBytes(audioFile, true, offset, &toRead, &pBuffer);
    if (kAudioFileEndOfFileError == err) {
        // cool, we're at the end of the file
        break;
    } else if (noErr != err) {
        // uh-oh, some error other than eof
        break;
    }
    // advance the next read offset
    offset += toRead;
    // advance the read buffer's pointer
    pBuffer += toRead;
    toRead = numBytes - offset;
    if (0 == toRead) {
        // got to the end of file but no eof err
        break;
    }
}

// Process audioBuffer ...

free(audioBuffer);
Art Gillespie
  • 8,747
  • 1
  • 37
  • 34
  • How do I determine how many bytes of data are available in the file? I want to define audioBuffer[] at that size, so I can extract all of the available data in one call. – MusiGenesis Dec 21 '12 at 20:50
  • I've updated my example (un-tested code, but it compiles) with a call to `AudioFileGetProperty` that demonstrates how to query for the number of audio data bytes. I also added a closer-to-real-world read loop. – Art Gillespie Dec 21 '12 at 21:23
  • Thanks. I ended up using your original, modified to just keep reading 4096-sized chunks until it reaches the end. I'm adding these bytes to an NSMutableData object anyway, so it wasn't hard to do. – MusiGenesis Dec 21 '12 at 22:04
  • 2
    Thanks :) Please note pBuffer is already a pointer and &pBuffer will produce an error https://stackoverflow.com/questions/17270024/oserror-40-while-converting-an-audio-file-to-byte-array-iphone – LordParsley Aug 13 '15 at 16:05
1

Here is something I stole from Getting NSData out of music file in iPhone and updated for ARC

- (NSData *)readSoundFileSamples:(NSString *)filePath
{

    // Get raw PCM data from the track
    NSURL *assetURL = [NSURL fileURLWithPath:filePath];
    NSMutableData *data = [[NSMutableData alloc] init];

    const uint32_t sampleRate = 16000; // 16k sample/sec
    const uint16_t bitDepth = 16; // 16 bit/sample/channel
    const uint16_t channels = 2; // 2 channel/sample (stereo)

    NSDictionary *opts = [NSDictionary dictionary];
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:assetURL options:opts];
    AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset error:NULL];
    NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
                              [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
                              [NSNumber numberWithFloat:(float)sampleRate], AVSampleRateKey,
                              [NSNumber numberWithInt:bitDepth], AVLinearPCMBitDepthKey,
                              [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,
                              [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey,
                              [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, nil];

    AVAssetReaderTrackOutput *output = [[AVAssetReaderTrackOutput alloc] initWithTrack:[[asset tracks] objectAtIndex:0] outputSettings:settings];
    [reader addOutput:output];
    [reader startReading];

    // read the samples from the asset and append them subsequently
    while ([reader status] != AVAssetReaderStatusCompleted) {
        CMSampleBufferRef buffer = [output copyNextSampleBuffer];
        if (buffer == NULL) continue;

        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(buffer);
        size_t size = CMBlockBufferGetDataLength(blockBuffer);
        uint8_t *outBytes = malloc(size);
        CMBlockBufferCopyDataBytes(blockBuffer, 0, size, outBytes);
        CMSampleBufferInvalidate(buffer);
        CFRelease(buffer);
        [data appendBytes:outBytes length:size];
        free(outBytes);
    }

    return data;

}
William Entriken
  • 37,208
  • 23
  • 149
  • 195