UPDATE: I have figured this out and posted my solution as an answer to my own question (below)
I am trying to write a simple buffer of audio samples to a file using ExtAudioFileWrite in AAC format.
I have achieved this with the code below to write a mono buffer to a .wav file - however, I cannot do this for stereo or for AAC files which is what I want to do.
Here is what I have so far...
CFStringRef fPath;
fPath = CFStringCreateWithCString(kCFAllocatorDefault,
"/path/to/my/audiofile/audiofile.wav",
kCFStringEncodingMacRoman);
OSStatus err;
int mChannels = 1;
UInt32 totalFramesInFile = 100000;
Float32 *outputBuffer = (Float32 *)malloc(sizeof(Float32) * (totalFramesInFile*mChannels));
////////////// Set up Audio Buffer List ////////////
AudioBufferList outputData;
outputData.mNumberBuffers = 1;
outputData.mBuffers[0].mNumberChannels = mChannels;
outputData.mBuffers[0].mDataByteSize = 4 * totalFramesInFile * mChannels;
outputData.mBuffers[0].mData = outputBuffer;
Float32 audioFile[totalFramesInFile*mChannels];
for (int i = 0;i < totalFramesInFile*mChannels;i++)
{
audioFile[i] = ((Float32)(rand() % 100))/100.0;
audioFile[i] = audioFile[i]*0.2;
}
outputData.mBuffers[0].mData = &audioFile;
CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,fPath,kCFURLPOSIXPathStyle,false);
ExtAudioFileRef audiofileRef;
// WAVE FILES
AudioFileTypeID fileType = kAudioFileWAVEType;
AudioStreamBasicDescription clientFormat;
clientFormat.mSampleRate = 44100.0;
clientFormat.mFormatID = kAudioFormatLinearPCM;
clientFormat.mFormatFlags = 12;
clientFormat.mBitsPerChannel = 16;
clientFormat.mChannelsPerFrame = mChannels;
clientFormat.mBytesPerFrame = 2*clientFormat.mChannelsPerFrame;
clientFormat.mFramesPerPacket = 1;
clientFormat.mBytesPerPacket = 2*clientFormat.mChannelsPerFrame;
// open the file for writing
err = ExtAudioFileCreateWithURL((CFURLRef)fileURL, fileType, &clientFormat, NULL, kAudioFileFlags_EraseFile, &audiofileRef);
if (err != noErr)
{
cout << "Problem when creating audio file: " << err << "\n";
}
// tell the ExtAudioFile API what format we'll be sending samples in
err = ExtAudioFileSetProperty(audiofileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat);
if (err != noErr)
{
cout << "Problem setting audio format: " << err << "\n";
}
UInt32 rFrames = (UInt32)totalFramesInFile;
// write the data
err = ExtAudioFileWrite(audiofileRef, rFrames, &outputData);
if (err != noErr)
{
cout << "Problem writing audio file: " << err << "\n";
}
// close the file
ExtAudioFileDispose(audiofileRef);
NSLog(@"Done!");
My specific questions are:
- How do I set up the AudioStreamBasicDescription for AAC?
- Why can't I get stereo to work properly here? If I set the number of channels ('mChannels') to 2 then I get the left channel correctly and distortion in the right channel.
I'd very much appreciate any help - I think I've read almost every page I can find on this and am none the wiser as, while there are similar questions, they usually derive the AudioStreamBasicDescription parameters from some input audio file, which I cannot see the result of. The Apple documentation is no help either.
Many thanks in advance,
Adam
Ok, after some exploration I have figured it out. I have wrapped it as a function that writes random noise to a file. Specifically, it can:
- write .wav or .m4a files
- write mono or stereo in either format
- write the file to a specified path
The function arguments are:
- path to audio file to be created
- number of channels (max 2)
- boolean: compress with m4a (if false, use pcm)
For a stereo M4A file, the function should be called as:
writeNoiseToAudioFile("/path/to/my/audiofile.m4a",2,true);
The source of the function follows. I have tried to comment it as much as possible - I hope it is correct, it certainly works for me, but please say "Adam, you've done this a bit wrong" if there is something I've missed. Good luck! Here is the code:
void writeNoiseToAudioFile(char *fName,int mChannels,bool compress_with_m4a)
{
OSStatus err; // to record errors from ExtAudioFile API functions
// create file path as CStringRef
CFStringRef fPath;
fPath = CFStringCreateWithCString(kCFAllocatorDefault,
fName,
kCFStringEncodingMacRoman);
// specify total number of samples per channel
UInt32 totalFramesInFile = 100000;
/////////////////////////////////////////////////////////////////////////////
////////////// Set up Audio Buffer List For Interleaved Audio ///////////////
/////////////////////////////////////////////////////////////////////////////
AudioBufferList outputData;
outputData.mNumberBuffers = 1;
outputData.mBuffers[0].mNumberChannels = mChannels;
outputData.mBuffers[0].mDataByteSize = sizeof(AudioUnitSampleType)*totalFramesInFile*mChannels;
/////////////////////////////////////////////////////////////////////////////
//////// Synthesise Noise and Put It In The AudioBufferList /////////////////
/////////////////////////////////////////////////////////////////////////////
// create an array to hold our audio
AudioUnitSampleType audioFile[totalFramesInFile*mChannels];
// fill the array with random numbers (white noise)
for (int i = 0;i < totalFramesInFile*mChannels;i++)
{
audioFile[i] = ((AudioUnitSampleType)(rand() % 100))/100.0;
audioFile[i] = audioFile[i]*0.2;
// (yes, I know this noise has a DC offset, bad)
}
// set the AudioBuffer to point to the array containing the noise
outputData.mBuffers[0].mData = &audioFile;
/////////////////////////////////////////////////////////////////////////////
////////////////// Specify The Output Audio File Format /////////////////////
/////////////////////////////////////////////////////////////////////////////
// the client format will describe the output audio file
AudioStreamBasicDescription clientFormat;
// the file type identifier tells the ExtAudioFile API what kind of file we want created
AudioFileTypeID fileType;
// if compress_with_m4a is tru then set up for m4a file format
if (compress_with_m4a)
{
// the file type identifier tells the ExtAudioFile API what kind of file we want created
// this creates a m4a file type
fileType = kAudioFileM4AType;
// Here we specify the M4A format
clientFormat.mSampleRate = 44100.0;
clientFormat.mFormatID = kAudioFormatMPEG4AAC;
clientFormat.mFormatFlags = kMPEG4Object_AAC_Main;
clientFormat.mChannelsPerFrame = mChannels;
clientFormat.mBytesPerPacket = 0;
clientFormat.mBytesPerFrame = 0;
clientFormat.mFramesPerPacket = 1024;
clientFormat.mBitsPerChannel = 0;
clientFormat.mReserved = 0;
}
else // else encode as PCM
{
// this creates a wav file type
fileType = kAudioFileWAVEType;
// This function audiomatically generates the audio format according to certain arguments
FillOutASBDForLPCM(clientFormat,44100.0,mChannels,32,32,true,false,false);
}
/////////////////////////////////////////////////////////////////////////////
///////////////// Specify The Format of Our Audio Samples ///////////////////
/////////////////////////////////////////////////////////////////////////////
// the local format describes the format the samples we will give to the ExtAudioFile API
AudioStreamBasicDescription localFormat;
FillOutASBDForLPCM (localFormat,44100.0,mChannels,32,32,true,false,false);
/////////////////////////////////////////////////////////////////////////////
///////////////// Create the Audio File and Open It /////////////////////////
/////////////////////////////////////////////////////////////////////////////
// create the audio file reference
ExtAudioFileRef audiofileRef;
// create a fileURL from our path
CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,fPath,kCFURLPOSIXPathStyle,false);
// open the file for writing
err = ExtAudioFileCreateWithURL((CFURLRef)fileURL, fileType, &clientFormat, NULL, kAudioFileFlags_EraseFile, &audiofileRef);
if (err != noErr)
{
cout << "Problem when creating audio file: " << err << "\n";
}
/////////////////////////////////////////////////////////////////////////////
///// Tell the ExtAudioFile API what format we'll be sending samples in /////
/////////////////////////////////////////////////////////////////////////////
// Tell the ExtAudioFile API what format we'll be sending samples in
err = ExtAudioFileSetProperty(audiofileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(localFormat), &localFormat);
if (err != noErr)
{
cout << "Problem setting audio format: " << err << "\n";
}
/////////////////////////////////////////////////////////////////////////////
///////// Write the Contents of the AudioBufferList to the AudioFile ////////
/////////////////////////////////////////////////////////////////////////////
UInt32 rFrames = (UInt32)totalFramesInFile;
// write the data
err = ExtAudioFileWrite(audiofileRef, rFrames, &outputData);
if (err != noErr)
{
cout << "Problem writing audio file: " << err << "\n";
}
/////////////////////////////////////////////////////////////////////////////
////////////// Close the Audio File and Get Rid Of The Reference ////////////
/////////////////////////////////////////////////////////////////////////////
// close the file
ExtAudioFileDispose(audiofileRef);
NSLog(@"Done!");
}
Don't forget to import the AudioToolbox Framework and to include the header file:
#import <AudioToolbox/AudioToolbox.h>
来源:https://stackoverflow.com/questions/12569015/writing-buffer-of-audio-samples-to-aac-file-using-extaudiofilewrite-for-ios