Skip to content

Instantly share code, notes, and snippets.

@konstantinpavlikhin
Last active June 8, 2022 10:02
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save konstantinpavlikhin/d08f98b14328baa5eddbdf98d0ab8b91 to your computer and use it in GitHub Desktop.
Save konstantinpavlikhin/d08f98b14328baa5eddbdf98d0ab8b91 to your computer and use it in GitHub Desktop.
Using VoiceProcessingIO for echo cancellation on macOS
OSStatus MyOutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData)
{
// Not being called at all.
}
@implementation XXXSpeakerDevice
{
AUGraph _graph;
}
- (void) dealloc
{
[self stop];
}
#define HANDLE_STATUS(x) if(x) { NSLog(@"OSStatus: %d", x); return NO; }
- (BOOL) start
{
OSStatus status = noErr;
// * * *.
// Graph initialization.
status = NewAUGraph(&_graph);
HANDLE_STATUS(status)
// * * *.
// Resampling node.
AudioComponentDescription resamplingAudioComponentDescription = [self resamplingAudioComponentDescription];
AUNode resamplingNode;
status = AUGraphAddNode(_graph, &resamplingAudioComponentDescription, &resamplingNode);
HANDLE_STATUS(status)
// * * *.
// Voice processing node.
AudioComponentDescription voiceProcessingAudioComponentDescription = [self voiceProcessingAudioComponentDescription];
AUNode voiceProcessingNode;
status = AUGraphAddNode(_graph, &voiceProcessingAudioComponentDescription, &voiceProcessingNode);
HANDLE_STATUS(status)
// * * *.
// (Resampling → voice processing) connection.
status = AUGraphConnectNodeInput(_graph, resamplingNode, 0, voiceProcessingNode, 0);
HANDLE_STATUS(status)
// * * *.
// Graph open.
status = AUGraphOpen(_graph);
HANDLE_STATUS(status)
// * * *.
// Resampling node input callback.
AURenderCallbackStruct inputCallback;
inputCallback.inputProc = MyOutputRenderCallback;
inputCallback.inputProcRefCon = (__bridge void* _Nullable)(self);
status = AUGraphSetNodeInputCallback(_graph, resamplingNode, 0, &inputCallback);
HANDLE_STATUS(status)
// * * *.
// Get the underlying audio unit from the resampling node.
AudioUnit resamplingAudioUnit;
status = AUGraphNodeInfo(_graph, resamplingNode, NULL, &resamplingAudioUnit);
HANDLE_STATUS(status)
// * * *.
// Get the underlying audio unit from the voice processing node.
AudioUnit voiceProcessingAudioUnit;
status = AUGraphNodeInfo(_graph, voiceProcessingNode, NULL, &voiceProcessingAudioUnit);
HANDLE_STATUS(status)
// * * *.
// 0 stands for 'muting off'.
const UInt32 value = 0;
status = AudioUnitSetProperty(voiceProcessingAudioUnit, kAUVoiceIOProperty_MuteOutput, kAudioUnitScope_Global, 1, &value, sizeof(value));
HANDLE_STATUS(status)
// * * *.
// Set resampling node's input stream format.
AudioStreamBasicDescription streamFormat = [self myStreamFormat];
status = AudioUnitSetProperty(resamplingAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(streamFormat));
HANDLE_STATUS(status)
// * * *.
{{
AudioStreamBasicDescription streamFormat;
UInt32 size = sizeof(streamFormat);
AudioUnitGetProperty(voiceProcessingAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, &size);
streamFormat.mChannelsPerFrame = 1;
// Set resampling node's output stream format.
status = AudioUnitSetProperty(resamplingAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, size);
HANDLE_STATUS(status)
}}
// * * *.
// Graph update.
status = AUGraphUpdate(_graph, NULL);
HANDLE_STATUS(status)
// * * *.
status = AUGraphInitialize(_graph);
HANDLE_STATUS(status)
// * * *.
status = AUGraphStart(_graph);
HANDLE_STATUS(status)
// * * *.
CAShow(_graph);
// * * *.
return YES;
}
- (AudioComponentDescription) voiceProcessingAudioComponentDescription
{
AudioComponentDescription outputComponentDescription;
outputComponentDescription.componentType = kAudioUnitType_Output;
outputComponentDescription.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
outputComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
outputComponentDescription.componentFlags = 0;
outputComponentDescription.componentFlagsMask = 0;
return outputComponentDescription;
}
- (AudioComponentDescription) resamplingAudioComponentDescription
{
AudioComponentDescription resamplingAudioComponentDescription;
resamplingAudioComponentDescription.componentType = kAudioUnitType_FormatConverter;
resamplingAudioComponentDescription.componentSubType = kAudioUnitSubType_AUConverter;
resamplingAudioComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
resamplingAudioComponentDescription.componentFlags = 0;
resamplingAudioComponentDescription.componentFlagsMask = 0;
return resamplingAudioComponentDescription;
}
- (AudioStreamBasicDescription) myStreamFormat
{
AudioStreamBasicDescription streamFormat;
UInt32 byteSize = sizeof(short);
streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger;
streamFormat.mSampleRate = 48000;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mBytesPerPacket = byteSize;
streamFormat.mFramesPerPacket = 1;
streamFormat.mBytesPerFrame = byteSize;
streamFormat.mChannelsPerFrame = 1; // Mono.
streamFormat.mBitsPerChannel = 8 * byteSize;
return streamFormat;
}
#define HANDLE_STATUS_NORETURN(x) if(x) { NSLog(@"OSStatus: %d", x); }
- (void) stop
{
if(_graph)
{
OSStatus status = noErr;
// * * *.
status = AUGraphStop(_graph);
HANDLE_STATUS_NORETURN(status)
// * * *.
status = AUGraphUninitialize(_graph);
HANDLE_STATUS_NORETURN(status)
// * * *.
status = AUGraphClose(_graph);
HANDLE_STATUS_NORETURN(status)
// * * *.
status = DisposeAUGraph(_graph);
HANDLE_STATUS_NORETURN(status)
// * * *.
_graph = NULL;
}
}
@mzhang94568
Copy link

I am developing an VOIP app on macOS. This code for echo cancellation works? Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment