Last active
June 8, 2022 10:02
-
-
Save konstantinpavlikhin/d08f98b14328baa5eddbdf98d0ab8b91 to your computer and use it in GitHub Desktop.
Using VoiceProcessingIO for echo cancellation on macOS
This file contains hidden or 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
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I am developing an VOIP app on macOS. This code for echo cancellation works? Thanks