-
-
Save goganchic/7a8960611dd2811e1d6c9d7ff258b9a4 to your computer and use it in GitHub Desktop.
Audio recording example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <AudioToolbox/AudioToolbox.h> | |
#define kNumberRecordBuffers 3 | |
#define kBufferDuration 0.5 | |
typedef struct MyRecorder { | |
AudioFileID recordFile; | |
SInt64 recordPacket; | |
Boolean running; | |
} MyRecorder; | |
static void CheckError(OSStatus error, const char *operation) { | |
if (error == noErr) return; | |
char errorString[20]; | |
*(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(error); | |
if (isprint(errorString[1]) && | |
isprint(errorString[2]) && | |
isprint(errorString[3]) && | |
isprint(errorString[4])) { | |
errorString[0] = errorString[5] = '\''; | |
errorString[6] = '\0'; | |
} else { | |
sprintf(errorString, "%d", (int)error); | |
} | |
fprintf(stderr, "Error: %s (%s)\n", operation, errorString); | |
exit(1); | |
} | |
OSStatus FillDefaultInputDeviceSampleRate(Float64 *outSampleRate) { | |
OSStatus error; | |
AudioDeviceID deviceID = 0; | |
AudioObjectPropertyAddress propertyAddress; | |
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; | |
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; | |
propertyAddress.mElement = kAudioObjectPropertyElementMaster; | |
UInt32 propertySize; | |
propertySize = sizeof(AudioDeviceID); | |
error = AudioObjectGetPropertyData(kAudioObjectSystemObject, | |
&propertyAddress, | |
0, | |
NULL, | |
&propertySize, | |
&deviceID); | |
if (error) return error; | |
AudioObjectPropertyAddress nameAddress = { | |
kAudioDevicePropertyDeviceName, | |
kAudioDevicePropertyScopeOutput, | |
kAudioObjectPropertyElementMaster | |
}; | |
char deviceName[128]; | |
UInt32 deviceNameLength = sizeof(deviceName); | |
error = AudioObjectGetPropertyData(deviceID, &nameAddress, 0, NULL, &deviceNameLength, deviceName); | |
if (error) return error; | |
printf("Default Device: %s\n", deviceName); | |
propertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate; | |
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; | |
propertyAddress.mElement = kAudioObjectPropertyElementMaster; | |
propertySize = sizeof(Float64); | |
error = AudioObjectGetPropertyData(deviceID, | |
&propertyAddress, | |
0, | |
NULL, | |
&propertySize, | |
outSampleRate); | |
return error; | |
} | |
static void CopyEncoderCookieToFile(AudioQueueRef queue, AudioFileID file) { | |
UInt32 propertySize; | |
CheckError(AudioQueueGetPropertySize(queue, | |
kAudioConverterCompressionMagicCookie, | |
&propertySize), | |
"Cookie size fetching failed"); | |
if (propertySize > 0) { | |
Byte *magicCookie = (Byte *)malloc(propertySize); | |
CheckError(AudioQueueGetProperty(queue, | |
kAudioQueueProperty_MagicCookie, | |
magicCookie, | |
&propertySize), | |
"Couldn't get audio queue's magic cookie"); | |
CheckError(AudioFileSetProperty(file, | |
kAudioFilePropertyMagicCookieData, | |
propertySize, | |
magicCookie), | |
"Couldn't set audio file's magic cookie"); | |
free(magicCookie); | |
} | |
} | |
static UInt32 ComputeRecordBufferSize(const AudioStreamBasicDescription *format, AudioQueueRef queue) { | |
UInt32 packets, frames, bytes; | |
frames = (int)ceil(kBufferDuration * format->mSampleRate); | |
if (format->mBytesPerFrame > 0) { | |
bytes = frames * format->mBytesPerFrame; | |
} else { | |
UInt32 maxPacketSize; | |
if (format->mBytesPerPacket > 0) { | |
maxPacketSize = format->mBytesPerPacket; | |
} else { | |
UInt32 propertySize = sizeof(maxPacketSize); | |
CheckError(AudioQueueGetProperty(queue, | |
kAudioConverterPropertyMaximumOutputPacketSize, | |
&maxPacketSize, | |
&propertySize), | |
"Couldn't get queue's maximum output packet size"); | |
} | |
if (format->mFramesPerPacket > 0) { | |
packets = frames / format->mFramesPerPacket; | |
} else { | |
packets = frames; | |
} | |
if (packets == 0) { | |
packets = 1; | |
} | |
bytes = packets * maxPacketSize; | |
} | |
return bytes; | |
} | |
static void MyAQInputCallback(void *inUserData, AudioQueueRef inQueue, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc) { | |
MyRecorder *recorder = (MyRecorder *)inUserData; | |
printf("Callback called, inNumPackets = %d, mAudioDataByteSize = %d\n", inNumPackets, inBuffer->mAudioDataByteSize); | |
if (inNumPackets > 0) { | |
CheckError(AudioFileWritePackets(recorder->recordFile, | |
FALSE, | |
inBuffer->mAudioDataByteSize, | |
inPacketDesc, | |
recorder->recordPacket, | |
&inNumPackets, | |
inBuffer->mAudioData), | |
"AudioFileWritePackets failed"); | |
recorder->recordPacket += inNumPackets; | |
} | |
if (recorder->running) { | |
CheckError(AudioQueueEnqueueBuffer(inQueue, inBuffer, 0, NULL), "AudioQueueEnqueueBuffer failed"); | |
} | |
} | |
int main(int argc, const char * argv[]) { | |
AudioStreamBasicDescription queueRecordFormat; | |
memset(&queueRecordFormat, 0, sizeof(queueRecordFormat)); | |
queueRecordFormat.mFormatID = kAudioFormatMPEG4AAC; | |
queueRecordFormat.mChannelsPerFrame = 2; | |
CheckError(FillDefaultInputDeviceSampleRate(&queueRecordFormat.mSampleRate), "Fetching default input device sample rate failed"); | |
printf("mSampleRate: %f\n", queueRecordFormat.mSampleRate); | |
UInt32 propSize = sizeof(queueRecordFormat); | |
CheckError(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, | |
0, | |
NULL, | |
&propSize, | |
&queueRecordFormat), | |
"AudioFormatGetProperty failed"); | |
MyRecorder recorder = {0}; | |
AudioQueueRef queue = {0}; | |
CheckError(AudioQueueNewInput(&queueRecordFormat, | |
MyAQInputCallback, | |
&recorder, | |
NULL, | |
NULL, | |
0, | |
&queue), | |
"AudioQueueNewInput failed"); | |
AudioStreamBasicDescription recordFormatForOutputFile; | |
UInt32 size = sizeof(recordFormatForOutputFile); | |
CheckError(AudioQueueGetProperty(queue, | |
kAudioConverterCurrentOutputStreamDescription, | |
&recordFormatForOutputFile, | |
&size), | |
"Couldn't get queue's format for file"); | |
CFURLRef outputFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, | |
CFSTR("output.caf"), | |
kCFURLPOSIXPathStyle, | |
false); | |
CheckError(AudioFileCreateWithURL(outputFileURL, | |
kAudioFileCAFType, | |
&recordFormatForOutputFile, | |
kAudioFileFlags_EraseFile, | |
&recorder.recordFile), | |
"AudioFileCreateWithURL failed"); | |
CFRelease(outputFileURL); | |
CopyEncoderCookieToFile(queue, recorder.recordFile); | |
UInt32 bufferByteSize = ComputeRecordBufferSize(&recordFormatForOutputFile, queue); | |
int bufferIndex; | |
for (bufferIndex = 0; bufferIndex < kNumberRecordBuffers; bufferIndex++) { | |
AudioQueueBufferRef buffer; | |
CheckError(AudioQueueAllocateBuffer(queue, | |
bufferByteSize, | |
&buffer), | |
"AudioQueueAllocateBuffer failed"); | |
CheckError(AudioQueueEnqueueBuffer(queue, | |
buffer, | |
0, | |
NULL), | |
"AudioQueueEnqueueBuffer failed"); | |
} | |
recorder.running = TRUE; | |
CheckError(AudioQueueStart(queue, NULL), "AudioQueueStart failed"); | |
printf("Recording, press <return> to stop:\n"); | |
getchar(); | |
printf("* recording done *\n"); | |
recorder.running = FALSE; | |
CheckError(AudioQueueStop(queue, TRUE), "AudioQueueStop failed"); | |
CopyEncoderCookieToFile(queue, recorder.recordFile); | |
AudioQueueDispose(queue, TRUE); | |
AudioFileClose(recorder.recordFile); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment