I need to convert PCM audio data with format:
Data format: 1 ch, 16000 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer
no channel layout.
estimated duration: 1.101063 sec
audio bytes: 35234
audio packets: 17617
bit rate: 256000 bits per second
packet size upper bound: 2
maximum packet size: 2
audio data file offset: 44
optimized
source bit depth: I16
to 16bit 2ch (stereo) 44100Hz PCM.
My input file comes as NSData and ideally would be if I could end up with NSData instead of saving output to file. I've seen many tutorials and examples of converting different audio formats but they seems very complicated and I'm wondering if there is any simple solution to do that. This is code I've tried so far:
-(void)convertAudioToRequiredFormat:(NSData *)data {
AudioFileID refAudioFileID;
ExtAudioFileRef inputFileID;
ExtAudioFileRef outputFileID;
OSStatus result = AudioFileOpenWithCallbacks((__bridge void *)(data), readProc, 0, getSizeProc, 0, kAudioFormatLinearPCM, &refAudioFileID);
if (result != noErr) {
DLog(@"error reading input audio file");
}
result = ExtAudioFileWrapAudioFileID(refAudioFileID, false, &inputFileID);
if (result != noErr){
DLog(@"problem in theAudioFileReaderWithData function Wraping the audio FileID: result code %i \n", (int)result);
}
AudioStreamBasicDescription clientFormat;
memset(&clientFormat, 0, sizeof(clientFormat));
clientFormat.mFormatID = kAudioFormatLinearPCM;
clientFormat.mSampleRate = 16000;
clientFormat.mFramesPerPacket = 1;
clientFormat.mBytesPerPacket = 2; //16 bits * 1 channel
clientFormat.mBytesPerFrame = 2;
clientFormat.mChannelsPerFrame = 1; //1 channel
clientFormat.mBitsPerChannel = 16;
clientFormat.mFormatFlags = kCAFLinearPCMFormatFlagIsLittleEndian;
clientFormat.mReserved = 0;
AudioStreamBasicDescription outputFormat;
memset(&outputFormat, 0, sizeof(outputFormat));
outputFormat.mFormatID = kAudioFormatLinearPCM;
outputFormat.mSampleRate = 44100;
outputFormat.mFramesPerPacket = 1; //it is always 1 for PCM
outputFormat.mBytesPerPacket = 4; //4 Bytes = 2 * 16 bits
outputFormat.mBytesPerFrame = 4;
outputFormat.mChannelsPerFrame = 2; //2 channels = stereo
outputFormat.mBitsPerChannel = 16; //16 bits per channel
outputFormat.mFormatFlags = kCAFLinearPCMFormatFlagIsLittleEndian;
clientFormat.mReserved = 0;
UInt32 outputFormatSize = sizeof(outputFormat);
result = 0;
result = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outputFormatSize, &outputFormat);
if(result != noErr)
NSLog(@"could not set the output format with status code %i \n",(int)result);
NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [docPaths objectAtIndex:0];
NSString *path = [docPath stringByAppendingPathComponent:@"newFormat.wav"];
CFURLRef sourceURL = (__bridge CFURLRef)[[NSURL alloc] initFileURLWithPath:path];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:path]) {
NSData *content = [NSData dataWithBytes:NULL length:0];
[fm createFileAtPath:path contents:content attributes:nil];
}
result = 0;
result = ExtAudioFileCreateWithURL(sourceURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &outputFileID);
if(result != noErr){
NSLog(@"ExtAudioFileCreateWithURL failed for outputFileID with status %i \n", (int)result);
}
int size = sizeof(clientFormat);
result = 0;
result = ExtAudioFileSetProperty(inputFileID, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat);
if(result != noErr)
NSLog(@"error on ExtAudioFileSetProperty for input File with result code %i \n", (int)result);
size = sizeof(clientFormat);
result = 0;
result = ExtAudioFileSetProperty(outputFileID, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat);
if(result != noErr)
NSLog(@"error on ExtAudioFileSetProperty for output File with result code %i \n", (int)result);
int totalFrames = 0;
UInt32 outputFilePacketPosition = 0; //in bytes
UInt32 encodedBytes = 0;
while (1) {
UInt32 bufferByteSize = 22050 * 4 * 2;
char srcBuffer[bufferByteSize];
UInt32 numFrames = (bufferByteSize/clientFormat.mBytesPerFrame);
AudioBufferList fillBufList;
fillBufList.mNumberBuffers = 1;
fillBufList.mBuffers[0].mNumberChannels = clientFormat.mChannelsPerFrame;
fillBufList.mBuffers[0].mDataByteSize = bufferByteSize;
fillBufList.mBuffers[0].mData = srcBuffer;
result = 0;
result = ExtAudioFileRead(inputFileID, &numFrames, &fillBufList);
if (result != noErr) {
NSLog(@"Error on ExtAudioFileRead with result code %i \n", (int)result);
totalFrames = 0;
break;
}
if (!numFrames)
break;
totalFrames = totalFrames + numFrames;
result = 0;
result = ExtAudioFileWrite(outputFileID,
numFrames,
&fillBufList);
if(result!= noErr){
NSLog(@"ExtAudioFileWrite failed with code %i \n", (int)result);
}
encodedBytes += numFrames * clientFormat.mBytesPerFrame;
}
//Clean up
ExtAudioFileDispose(inputFileID);
ExtAudioFileDispose(outputFileID);
AudioFileClose(refAudioFileID);
}
static OSStatus readProc(void* clientData, SInt64 position, UInt32 requestCount, void* buffer, UInt32* actualCount)
{
NSData *inAudioData = (__bridge NSData *) clientData;
size_t dataSize = inAudioData.length;
size_t bytesToRead = 0;
if(position < dataSize) {
size_t bytesAvailable = dataSize - position;
bytesToRead = requestCount <= bytesAvailable ? requestCount : bytesAvailable;
[inAudioData getBytes: buffer range:NSMakeRange(position, bytesToRead)];
} else {
NSLog(@"data was not read \n");
bytesToRead = 0;
}
if(actualCount)
*actualCount = bytesToRead;
return noErr;
}
static SInt64 getSizeProc(void* clientData) {
NSData *inAudioData = (__bridge NSData *) clientData;
size_t dataSize = inAudioData.length;
return dataSize;
}
Unfortunately it doesn't work and I'm ending up with EXC_BAD_ACCESS in that line:
result = ExtAudioFileCreateWithURL(sourceURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &outputFileID);
I have no idea what's causing that error (wrong AudioStreamBasicDescription?). Can someone help me to fix it? Or maybe there is easier way to convert that audio data to desired PCM format?