Skip to content

Instantly share code, notes, and snippets.

@goganchic

goganchic/main.m Secret

Created October 21, 2018 14:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goganchic/7a8960611dd2811e1d6c9d7ff258b9a4 to your computer and use it in GitHub Desktop.
Save goganchic/7a8960611dd2811e1d6c9d7ff258b9a4 to your computer and use it in GitHub Desktop.
Audio recording example
#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