// ____________________________________________________________________________________ // main program int main(int argc, const char *argv[]) { const char *recordFileName = NULL; int i, nchannels, bufferByteSize; float seconds = 0; AudioStreamBasicDescription recordFormat; MyRecorder aqr; UInt32 size; CFURLRef url; OSStatus err = noErr; // fill structures with 0/NULL memset(&recordFormat, 0, sizeof(recordFormat)); memset(&aqr, 0, sizeof(aqr)); // parse arguments for (i = 1; i < argc; ++i) { const char *arg = argv[i]; if (arg[0] == '-') { switch (arg[1]) { case 'c': if (++i == argc) MissingArgument(arg); if (sscanf(argv[i], "%d", &nchannels) != 1) usage(); recordFormat.mChannelsPerFrame = nchannels; break; case 'd': if (++i == argc) MissingArgument(arg); if (StrTo4CharCode(argv[i], &recordFormat.mFormatID) == 0) ParseError(arg, argv[i]); break; case 'r': if (++i == argc) MissingArgument(arg); if (sscanf(argv[i], "%lf", &recordFormat.mSampleRate) != 1) ParseError(arg, argv[i]); break; case 's': if (++i == argc) MissingArgument(arg); if (sscanf(argv[i], "%f", &seconds) != 1) ParseError(arg, argv[i]); break; case 'v': aqr.verbose = TRUE; break; default: fprintf(stderr, "unknown option: '%s'\n\n", arg); usage(); } } else if (recordFileName != NULL) { fprintf(stderr, "may only specify one file to record\n\n"); usage(); } else recordFileName = arg; } if (recordFileName == NULL) // no record file path provided usage(); // determine file format AudioFileTypeID audioFileType = kAudioFileCAFType; // default to CAF CFStringRef cfRecordFileName = CFStringCreateWithCString(NULL, recordFileName, kCFStringEncodingUTF8); InferAudioFileFormatFromFilename(cfRecordFileName, &audioFileType); CFRelease(cfRecordFileName); // adapt record format to hardware and apply defaults if (recordFormat.mSampleRate == 0.) MyGetDefaultInputDeviceSampleRate(&recordFormat.mSampleRate); if (recordFormat.mChannelsPerFrame == 0) recordFormat.mChannelsPerFrame = 2; if (recordFormat.mFormatID == 0 || recordFormat.mFormatID == kAudioFormatLinearPCM) { // default to PCM, 16 bit int recordFormat.mFormatID = kAudioFormatLinearPCM; recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; recordFormat.mBitsPerChannel = 16; if (MyFileFormatRequiresBigEndian(audioFileType, recordFormat.mBitsPerChannel)) recordFormat.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; recordFormat.mBytesPerPacket = recordFormat.mBytesPerFrame = (recordFormat.mBitsPerChannel / 8) * recordFormat.mChannelsPerFrame; recordFormat.mFramesPerPacket = 1; recordFormat.mReserved = 0; } try { // create the queue XThrowIfError(AudioQueueNewInput( &recordFormat, MyInputBufferHandler, &aqr /* userData */, NULL /* run loop */, NULL /* run loop mode */, 0 /* flags */, &aqr.queue), "AudioQueueNewInput failed"); // get the record format back from the queue's audio converter -- // the file may require a more specific stream description than was necessary to create the encoder. size = sizeof(recordFormat); XThrowIfError(AudioQueueGetProperty(aqr.queue, kAudioConverterCurrentOutputStreamDescription, &recordFormat, &size), "couldn't get queue's format"); // convert recordFileName from C string to CFURL url = CFURLCreateFromFileSystemRepresentation(NULL, (Byte *)recordFileName, strlen(recordFileName), FALSE); XThrowIfError(!url, "couldn't create record file"); // create the audio file err = AudioFileCreateWithURL(url, audioFileType, &recordFormat, kAudioFileFlags_EraseFile, &aqr.recordFile); CFRelease(url); // release first, and then bail out on error XThrowIfError(err, "AudioFileCreateWithURL failed"); // copy the cookie first to give the file object as much info as we can about the data going in MyCopyEncoderCookieToFile(aqr.queue, aqr.recordFile); // allocate and enqueue buffers bufferByteSize = MyComputeRecordBufferSize(&recordFormat, aqr.queue, 0.5); // enough bytes for half a second for (i = 0; i < kNumberRecordBuffers; ++i) { AudioQueueBufferRef buffer; XThrowIfError(AudioQueueAllocateBuffer(aqr.queue, bufferByteSize, &buffer), "AudioQueueAllocateBuffer failed"); XThrowIfError(AudioQueueEnqueueBuffer(aqr.queue, buffer, 0, NULL), "AudioQueueEnqueueBuffer failed"); } // record if (seconds > 0) { // user requested a fixed-length recording (specified a duration with -s) // to time the recording more accurately, watch the queue's IsRunning property XThrowIfError(AudioQueueAddPropertyListener(aqr.queue, kAudioQueueProperty_IsRunning, MyPropertyListener, &aqr), "AudioQueueAddPropertyListener failed"); // start the queue aqr.running = TRUE; XThrowIfError(AudioQueueStart(aqr.queue, NULL), "AudioQueueStart failed"); CFAbsoluteTime waitUntil = CFAbsoluteTimeGetCurrent() + 10; // wait for the started notification while (aqr.queueStartStopTime == 0.) { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.010, FALSE); if (CFAbsoluteTimeGetCurrent() >= waitUntil) { fprintf(stderr, "Timeout waiting for the queue's IsRunning notification\n"); goto cleanup; } } printf("Recording...\n"); CFAbsoluteTime stopTime = aqr.queueStartStopTime + seconds; CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); CFRunLoopRunInMode(kCFRunLoopDefaultMode, stopTime - now, FALSE); } else { // start the queue aqr.running = TRUE; XThrowIfError(AudioQueueStart(aqr.queue, NULL), "AudioQueueStart failed"); // and wait printf("Recording, press <return> to stop:\n"); getchar(); } // end recording printf("* recording done *\n"); aqr.running = FALSE; XThrowIfError(AudioQueueStop(aqr.queue, TRUE), "AudioQueueStop failed"); // a codec may update its cookie at the end of an encoding session, so reapply it to the file now MyCopyEncoderCookieToFile(aqr.queue, aqr.recordFile); cleanup: AudioQueueDispose(aqr.queue, TRUE); AudioFileClose(aqr.recordFile); } catch (CAXException e) { char buf[256]; fprintf(stderr, "MyInputBufferHandler: %s (%s)\n", e.mOperation, e.FormatError(buf)); return e.mError; } return 0; }
int main(int argc, const char *argv[]) { MyRecorder recorder = {0}; AudioStreamBasicDescription recordFormat = {0}; memset(&recordFormat, 0, sizeof(recordFormat)); // Configure the output data format to be AAC recordFormat.mFormatID = kAudioFormatMPEG4AAC; recordFormat.mChannelsPerFrame = 2; // get the sample rate of the default input device // we use this to adapt the output data format to match hardware capabilities MyGetDefaultInputDeviceSampleRate(&recordFormat.mSampleRate); // ProTip: Use the AudioFormat API to trivialize ASBD creation. // input: atleast the mFormatID, however, at this point we already have // mSampleRate, mFormatID, and mChannelsPerFrame // output: the remainder of the ASBD will be filled out as much as possible // given the information known about the format UInt32 propSize = sizeof(recordFormat); CheckError(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &propSize, &recordFormat), "AudioFormatGetProperty failed"); // create a input (recording) queue AudioQueueRef queue = {0}; CheckError(AudioQueueNewInput(&recordFormat, // ASBD MyAQInputCallback, // Callback &recorder, // user data NULL, // run loop NULL, // run loop mode 0, // flags (always 0) // &recorder.queue), // output: reference to AudioQueue object &queue), "AudioQueueNewInput failed"); // since the queue is now initilized, we ask it's Audio Converter object // for the ASBD it has configured itself with. The file may require a more // specific stream description than was necessary to create the audio queue. // // for example: certain fields in an ASBD cannot possibly be known until it's // codec is instantiated (in this case, by the AudioQueue's Audio Converter object) UInt32 size = sizeof(recordFormat); CheckError(AudioQueueGetProperty(queue, kAudioConverterCurrentOutputStreamDescription, &recordFormat, &size), "couldn't get queue's format"); // create the audio file CFURLRef myFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("./output.caf"), kCFURLPOSIXPathStyle, false); CFShow (myFileURL); CheckError(AudioFileCreateWithURL(myFileURL, kAudioFileCAFType, &recordFormat, kAudioFileFlags_EraseFile, &recorder.recordFile), "AudioFileCreateWithURL failed"); CFRelease(myFileURL); // many encoded formats require a 'magic cookie'. we set the cookie first // to give the file object as much info as we can about the data it will be receiving MyCopyEncoderCookieToFile(queue, recorder.recordFile); // allocate and enqueue buffers int bufferByteSize = MyComputeRecordBufferSize(&recordFormat, queue, 0.5); // enough bytes for half a second 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"); } // start the queue. this function return immedatly and begins // invoking the callback, as needed, asynchronously. recorder.running = TRUE; CheckError(AudioQueueStart(queue, NULL), "AudioQueueStart failed"); // and wait printf("Recording, press <return> to stop:\n"); getchar(); // end recording printf("* recording done *\n"); recorder.running = FALSE; CheckError(AudioQueueStop(queue, TRUE), "AudioQueueStop failed"); // a codec may update its magic cookie at the end of an encoding session // so reapply it to the file now MyCopyEncoderCookieToFile(queue, recorder.recordFile); cleanup: AudioQueueDispose(queue, TRUE); AudioFileClose(recorder.recordFile); return 0; }