Skip to content

Instantly share code, notes, and snippets.

@AlexanderBollbach
Created January 11, 2016 07:03
Show Gist options
  • Save AlexanderBollbach/2dbf971775c1fde985c0 to your computer and use it in GitHub Desktop.
Save AlexanderBollbach/2dbf971775c1fde985c0 to your computer and use it in GitHub Desktop.
//#include <AudioToolbox/AudioToolbox.h>
#import <AudioToolbox/AudioToolbox.h>
#include <ApplicationServices/ApplicationServices.h>
#include "CARingBuffer.h"
#include <pthread.h>
#include <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
//#define PART_II
typedef struct MyAUGraphPlayer
{
AudioStreamBasicDescription streamFormat;
AUGraph graph;
AudioUnit inputUnit;
AudioUnit outputUnit;
#ifdef PART_II
AudioUnit speechUnit;
#endif
AudioBufferList *inputBuffer;
CARingBuffer *ringBuffer;
Float64 firstInputSampleTime;
Float64 firstOutputSampleTime;
Float64 inToOutSampleTimeOffset;
AudioFileID recordFile;
SInt64 startingByte;
} MyAUGraphPlayer;
OSStatus InputRenderProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * ioData);
OSStatus GraphRenderProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * ioData);
void CreateInputUnit (MyAUGraphPlayer *player);
void CreateMyAUGraph(MyAUGraphPlayer *player);
#pragma mark - render proc -
OSStatus InputRenderProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * ioData)
{
// printf ("InputRenderProc!\n");
MyAUGraphPlayer *player = (MyAUGraphPlayer*) inRefCon;
// have we ever logged input timing? (for offset calculation)
if (player->firstInputSampleTime < 0.0) {
player->firstInputSampleTime = inTimeStamp->mSampleTime;
if ((player->firstOutputSampleTime > -1.0) &&
(player->inToOutSampleTimeOffset < 0.0)) {
player->inToOutSampleTimeOffset = player->firstInputSampleTime - player->firstOutputSampleTime;
}
}
// render into our buffer
OSStatus inputProcErr = noErr;
inputProcErr = AudioUnitRender(player->inputUnit,
ioActionFlags,
inTimeStamp,
inBusNumber,
inNumberFrames,
player->inputBuffer);
Float32 someDataL = *(Float32*)(player->inputBuffer->mBuffers[0].mData);
printf("L2 input: % 1.7f \n",someDataL);
UInt32 numOfBytes = 4096*player->streamFormat.mBytesPerFrame;
AudioFileWriteBytes(player->recordFile,
FALSE,
player->startingByte,
&numOfBytes,
&ioData[0].mBuffers[0].mData);
player->startingByte += numOfBytes;
if (! inputProcErr) {
inputProcErr = player->ringBuffer->Store(player->inputBuffer,
inNumberFrames,
inTimeStamp->mSampleTime);
printf ("stored %d frames at time %f\n", inNumberFrames, inTimeStamp->mSampleTime);
}
return inputProcErr;
}
OSStatus GraphRenderProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList * ioData)
{
// printf ("graph!\n");
// printf ("GraphRenderProc! need %d frames for time %f \n", inNumberFrames, inTimeStamp->mSampleTime);
MyAUGraphPlayer *player = (MyAUGraphPlayer*) inRefCon;
Float32 someDataL = *(Float32*)(player->inputBuffer->mBuffers[0].mData);
// Float32 someDataR = *(Float32*)(player->inputBuffer->mBuffers[1].mData);
// printf("L: % 6.4f || R: % 6.4f \n", someDataL,someDataR);
// printf("L input: % 1.7f \n",someDataL);
// have we ever logged output timing? (for offset calculation)
if (player->firstOutputSampleTime < 0.0) {
player->firstOutputSampleTime = inTimeStamp->mSampleTime;
if ((player->firstInputSampleTime > -1.0) &&
(player->inToOutSampleTimeOffset < 0.0)) {
player->inToOutSampleTimeOffset = player->firstInputSampleTime - player->firstOutputSampleTime;
}
}
// copy samples out of ring buffer
OSStatus outputProcErr = noErr;
// new CARingBuffer doesn't take bool 4th arg
outputProcErr = player->ringBuffer->Fetch(ioData,
inNumberFrames,
inTimeStamp->mSampleTime + player->inToOutSampleTimeOffset);
// printf ("fetched %d frames at time %f\n", inNumberFrames, inTimeStamp->mSampleTime);
return outputProcErr;
}
#pragma mark - utility functions -
// generic error handler - if err is nonzero, prints error message and exits program.
static void CheckError(OSStatus error, const char *operation)
{
if (error == noErr) return;
char str[20];
// see if it appears to be a 4-char-code
*(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error);
if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) {
str[0] = str[5] = '\'';
str[6] = '\0';
} else
// no, format it as an integer
sprintf(str, "%d", (int)error);
fprintf(stderr, "Error: %s (%s)\n", operation, str);
exit(1);
}
void CreateInputUnit (MyAUGraphPlayer *player) {
// generate description that will match audio HAL
AudioComponentDescription inputcd = {0};
inputcd.componentType = kAudioUnitType_Output;
inputcd.componentSubType = kAudioUnitSubType_HALOutput;
inputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent comp = AudioComponentFindNext(NULL, &inputcd);
if (comp == NULL) {
printf ("can't get output unit");
exit (-1);
}
CheckError(AudioComponentInstanceNew(comp, &player->inputUnit),
"Couldn't open component for inputUnit");
// enable/io
UInt32 disableFlag = 0;
UInt32 enableFlag = 1;
AudioUnitScope outputBus = 0;
AudioUnitScope inputBus = 1;
CheckError (AudioUnitSetProperty(player->inputUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
inputBus,
&enableFlag,
sizeof(enableFlag)),
"Couldn't enable input on I/O unit");
CheckError (AudioUnitSetProperty(player->inputUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
outputBus,
&disableFlag, // well crap, have to disable
sizeof(enableFlag)),
"Couldn't disable output on I/O unit");
// set device (osx only... iphone has only one device)
AudioDeviceID defaultDevice = kAudioObjectUnknown;
UInt32 propertySize = sizeof (defaultDevice);
// AudioHardwareGetProperty() is deprecated
// CheckError (AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice,
// &propertySize,
// &defaultDevice),
// "Couldn't get default input device");
// AudioObjectProperty stuff new in 10.6, replaces AudioHardwareGetProperty() call
// TODO: need to update ch08 to explain, use this call. need CoreAudio.framework
AudioObjectPropertyAddress defaultDeviceProperty;
defaultDeviceProperty.mSelector = kAudioHardwarePropertyDefaultInputDevice;
defaultDeviceProperty.mScope = kAudioObjectPropertyScopeGlobal;
defaultDeviceProperty.mElement = kAudioObjectPropertyElementMaster;
CheckError (AudioObjectGetPropertyData(kAudioObjectSystemObject,
&defaultDeviceProperty,
0,
NULL,
&propertySize,
&defaultDevice),
"Couldn't get default input device");
UInt32 bufferProp = 4096;
AudioObjectPropertyAddress defaultDeviceProperty2;
defaultDeviceProperty2.mSelector = kAudioDevicePropertyBufferFrameSize;
defaultDeviceProperty2.mScope = kAudioObjectPropertyScopeGlobal;
defaultDeviceProperty2.mElement = kAudioObjectPropertyElementMaster;
AudioObjectSetPropertyData(defaultDevice,
&defaultDeviceProperty2,
NULL,
NULL,
sizeof(bufferProp),
&bufferProp);
// set this defaultDevice as the input's property
// kAudioUnitErr_InvalidPropertyValue if output is enabled on inputUnit
CheckError(AudioUnitSetProperty(player->inputUnit,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
outputBus,
&defaultDevice,
sizeof(defaultDevice)),
"Couldn't set default device on I/O unit");
// use the stream format coming out of the AUHAL (should be de-interleaved)
propertySize = sizeof (AudioStreamBasicDescription);
CheckError(AudioUnitGetProperty(player->inputUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
inputBus,
&player->streamFormat,
&propertySize),
"Couldn't get ASBD from input unit");
// 9/6/10 - check the input device's stream format
AudioStreamBasicDescription deviceFormat;
CheckError(AudioUnitGetProperty(player->inputUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
inputBus,
&deviceFormat,
&propertySize),
"Couldn't get ASBD from input unit");
printf ("Device rate %f, graph rate %f\n",
deviceFormat.mSampleRate,
player->streamFormat.mSampleRate);
player->streamFormat.mSampleRate = deviceFormat.mSampleRate;
propertySize = sizeof (AudioStreamBasicDescription);
CheckError(AudioUnitSetProperty(player->inputUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
inputBus,
&player->streamFormat,
propertySize), "Couldn't set ASBD on input unit");
UInt32 bufferSizeFrames = 0;
propertySize = sizeof(UInt32);
CheckError (AudioUnitGetProperty(player->inputUnit,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global,
0,
&bufferSizeFrames,
&propertySize), "Couldn't get buffer frame size from input unit");
UInt32 bufferSizeBytes = bufferSizeFrames * sizeof(Float32);
printf("buffer num of frames %i", bufferSizeFrames);
if (player->streamFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) {
int offset = offsetof(AudioBufferList, mBuffers[0]);
int sizeOfAB = sizeof(AudioBuffer);
int chNum = player->streamFormat.mChannelsPerFrame;
int inputBufferSize = offset + sizeOfAB * chNum;
//malloc buffer lists
player->inputBuffer = (AudioBufferList *)malloc(inputBufferSize);
player->inputBuffer->mNumberBuffers = chNum;
for (UInt32 i = 0; i < chNum ; i++) {
player->inputBuffer->mBuffers[i].mNumberChannels = 1;
player->inputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes;
player->inputBuffer->mBuffers[i].mData = malloc(bufferSizeBytes);
}
}
// else {
// printf ("format is interleaved\n");
// // allocate an AudioBufferList plus enough space for array of AudioBuffers
// UInt32 propsize = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * 1);
//
//
//
// //malloc buffer lists
// player->inputBuffer = (AudioBufferList *)malloc(propsize);
// player->inputBuffer->mNumberBuffers = 1;
//
// //pre-malloc buffers for AudioBufferLists
// player->inputBuffer->mBuffers[0].mNumberChannels = player->streamFormat.mChannelsPerFrame;
// player->inputBuffer->mBuffers[0].mDataByteSize = bufferSizeBytes;
// player->inputBuffer->mBuffers[0].mData = malloc(bufferSizeBytes);
// }
//Alloc ring buffer that will hold data between the two audio devices
player->ringBuffer = new CARingBuffer();
player->ringBuffer->Allocate(player->streamFormat.mChannelsPerFrame,
player->streamFormat.mBytesPerFrame,
bufferSizeFrames * 3);
// set render proc to supply samples from input unit
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = InputRenderProc;
callbackStruct.inputProcRefCon = player;
CheckError(AudioUnitSetProperty(player->inputUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
0,
&callbackStruct,
sizeof(callbackStruct)),
"Couldn't set input callback");
CheckError(AudioUnitInitialize(player->inputUnit),
"Couldn't initialize input unit");
player->firstInputSampleTime = -1;
player->inToOutSampleTimeOffset = -1;
printf ("Bottom of CreateInputUnit()\n");
}
void CreateMyAUGraph(MyAUGraphPlayer *player)
{
// create a new AUGraph
CheckError(NewAUGraph(&player->graph),
"NewAUGraph failed");
// generate description that will match default output
// ComponentDescription outputcd = {0};
// outputcd.componentType = kAudioUnitType_Output;
// outputcd.componentSubType = kAudioUnitSubType_DefaultOutput;
// outputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
//
// Component comp = FindNextComponent(NULL, &outputcd);
// if (comp == NULL) {
// printf ("can't get output unit"); exit (-1);
// }
AudioComponentDescription outputcd = {0};
outputcd.componentType = kAudioUnitType_Output;
outputcd.componentSubType = kAudioUnitSubType_DefaultOutput;
outputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
AudioComponent comp = AudioComponentFindNext(NULL, &outputcd);
if (comp == NULL) {
printf ("can't get output unit"); exit (-1);
}
// adds a node with above description to the graph
AUNode outputNode;
CheckError(AUGraphAddNode(player->graph, &outputcd, &outputNode),
"AUGraphAddNode[kAudioUnitSubType_DefaultOutput] failed");
#ifdef PART_II
// add a mixer to the graph,
AudioComponentDescription mixercd = {0};
mixercd.componentType = kAudioUnitType_Mixer;
mixercd.componentSubType = kAudioUnitSubType_StereoMixer; // doesn't work: kAudioUnitSubType_MatrixMixer
mixercd.componentManufacturer = kAudioUnitManufacturer_Apple;
AUNode mixerNode;
CheckError(AUGraphAddNode(player->graph, &mixercd, &mixerNode),
"AUGraphAddNode[kAudioUnitSubType_StereoMixer] failed");
// adds a node with above description to the graph
AudioComponentDescription speechcd = {0};
speechcd.componentType = kAudioUnitType_Generator;
speechcd.componentSubType = kAudioUnitSubType_SpeechSynthesis;
speechcd.componentManufacturer = kAudioUnitManufacturer_Apple;
AUNode speechNode;
CheckError(AUGraphAddNode(player->graph, &speechcd, &speechNode),
"AUGraphAddNode[kAudioUnitSubType_AudioFilePlayer] failed");
// opening the graph opens all contained audio units but does not allocate any resources yet
CheckError(AUGraphOpen(player->graph),
"AUGraphOpen failed");
// get the reference to the AudioUnit objects for the various nodes
CheckError(AUGraphNodeInfo(player->graph, outputNode, NULL, &player->outputUnit),
"AUGraphNodeInfo failed");
CheckError(AUGraphNodeInfo(player->graph, speechNode, NULL, &player->speechUnit),
"AUGraphNodeInfo failed");
AudioUnit mixerUnit;
CheckError(AUGraphNodeInfo(player->graph, mixerNode, NULL, &mixerUnit),
"AUGraphNodeInfo failed");
// set ASBDs here
UInt32 propertySize = sizeof (AudioStreamBasicDescription);
CheckError(AudioUnitSetProperty(player->outputUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&player->streamFormat,
propertySize),
"Couldn't set stream format on output unit");
// problem: badComponentInstance (-2147450879)
CheckError(AudioUnitSetProperty(mixerUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&player->streamFormat,
propertySize),
"Couldn't set stream format on mixer unit bus 0");
CheckError(AudioUnitSetProperty(mixerUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
1,
&player->streamFormat,
propertySize),
"Couldn't set stream format on mixer unit bus 1");
// connections
// mixer output scope / bus 0 to outputUnit input scope / bus 0
// mixer input scope / bus 0 to render callback (from ringbuffer, which in turn is from inputUnit)
// mixer input scope / bus 1 to speech unit output scope / bus 0
CheckError(AUGraphConnectNodeInput(player->graph, mixerNode, 0, outputNode, 0),
"Couldn't connect mixer output(0) to outputNode (0)");
CheckError(AUGraphConnectNodeInput(player->graph, speechNode, 0, mixerNode, 1),
"Couldn't connect speech synth unit output (0) to mixer input (1)");
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = GraphRenderProc;
callbackStruct.inputProcRefCon = player;
CheckError(AudioUnitSetProperty(mixerUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global,
0,
&callbackStruct,
sizeof(callbackStruct)),
"Couldn't set render callback on mixer unit");
#else
// opening the graph opens all contained audio units but does not allocate any resources yet
CheckError(AUGraphOpen(player->graph),
"AUGraphOpen failed");
// get the reference to the AudioUnit object for the output graph node
CheckError(AUGraphNodeInfo(player->graph, outputNode, NULL, &player->outputUnit),
"AUGraphNodeInfo failed");
// set the stream format on the output unit's input scope
UInt32 propertySize = sizeof (AudioStreamBasicDescription);
CheckError(AudioUnitSetProperty(player->outputUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&player->streamFormat,
propertySize),
"Couldn't set stream format on output unit");
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = GraphRenderProc;
callbackStruct.inputProcRefCon = player;
CheckError(AudioUnitSetProperty(player->outputUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global,
0,
&callbackStruct,
sizeof(callbackStruct)),
"Couldn't set render callback on output unit");
#endif
// now initialize the graph (causes resources to be allocated)
CheckError(AUGraphInitialize(player->graph),
"AUGraphInitialize failed");
player->firstOutputSampleTime = -1;
printf ("Bottom of CreateSimpleAUGraph()\n");
}
#ifdef PART_II
void PrepareSpeechAU(MyAUGraphPlayer *player)
{
SpeechChannel chan;
UInt32 propsize = sizeof(SpeechChannel);
CheckError(AudioUnitGetProperty(player->speechUnit, kAudioUnitProperty_SpeechChannel,
kAudioUnitScope_Global, 0, &chan, &propsize), "AudioFileGetProperty[kAudioUnitProperty_SpeechChannel] failed");
SpeakCFString(chan,
CFSTR("Please purchase as many copies of our\
Core Audio book as you possibly can"),
NULL);
}
#endif
int main(void) {
//
// AudioDeviceID myDevice;
// OSStatus err;
// //get the default output device
// AudioObjectPropertyAddress addr;
// UInt32 size;
// addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
// addr.mScope = kAudioObjectPropertyScopeGlobal;
// addr.mElement = 0;
// size = sizeof(AudioDeviceID);
// err = AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, &size, &myDevice);
//
// //get its sample rate
// addr.mSelector = kAudioDevicePropertyNominalSampleRate;
// addr.mScope = kAudioObjectPropertyScopeGlobal;
// addr.mElement = 0;
// size = sizeof(Float64);
// Float64 outSampleRate;
// err = AudioHardwareServiceGetPropertyData(myDevice, &addr, 0, NULL, &size, &outSampleRate);
// //if there is no error, outSampleRate contains the sample rate
//
//
// printf("samplerate: %f", outSampleRate);
// struct contains audiofileID as member
MyAUGraphPlayer player = {0};
player.startingByte = 0;
memset (&player, 0, sizeof (player));
// describe a PCM format for audio file
AudioStreamBasicDescription format = { 0 };
format.mBytesPerFrame = 2;
format.mBytesPerPacket = 2;
format.mChannelsPerFrame = 1;
format.mBitsPerChannel = 16;
format.mFramesPerPacket = 1;
format.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat;
format.mFormatID = kAudioFormatLinearPCM;
CFURLRef myFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("./test.wav"), kCFURLPOSIXPathStyle, false);
//CFShow (myFileURL);
CheckError(AudioFileCreateWithURL(myFileURL,
kAudioFileWAVEType,
&format,
kAudioFileFlags_EraseFile,
&player.recordFile), "AudioFileCreateWithURL failed");
// create the input unit
CreateInputUnit(&player);
// build a graph with output unit
CreateMyAUGraph(&player);
#ifdef PART_II
// configure the speech synthesizer
PrepareSpeechAU(&player);
#endif
// start playing
CheckError (AudioOutputUnitStart(player.inputUnit), "AudioOutputUnitStart failed");
CheckError(AUGraphStart(player.graph), "AUGraphStart failed");
// and wait
printf("Capturing, press <return> to stop:\n");
getchar();
cleanup:
AudioFileClose(player.recordFile);
AUGraphStop (player.graph);
AUGraphUninitialize (player.graph);
AUGraphClose(player.graph);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment