Skip to content

Instantly share code, notes, and snippets.

@p-i-
Created October 13, 2024 01:12
Show Gist options
  • Save p-i-/cec37b6c627713849b7bc850a91c74e1 to your computer and use it in GitHub Desktop.
Save p-i-/cec37b6c627713849b7bc850a91c74e1 to your computer and use it in GitHub Desktop.
AEC on macOS
// clang++ -o x x.cpp -framework CoreAudio -framework AudioToolbox
#include <iostream>
#include <cmath>
#include <fstream>
#include <AudioToolbox/AudioToolbox.h>
constexpr float kSineFrequency = 440.0f; // 440Hz sinewave
constexpr float kSampleRate = 48000.0f; // Sample rate
// Files with raw float32 data
std::ofstream file("file.f32");
// Sinewave generator
float generateSinewave(float phase) {
return std::sin(phase * 2.0 * M_PI); // Sinewave output
}
OSStatus inputCallback(void* opaque, AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* inTimeStamp, uint32_t inBusNumber,
uint32_t inNumberFrames, AudioBufferList* ioData) {
AudioUnit unit = *reinterpret_cast<AudioUnit*>(opaque);
AudioBufferList list;
list.mNumberBuffers = 1;
list.mBuffers[0].mNumberChannels = 1;
list.mBuffers[0].mDataByteSize = static_cast<uint32_t>(inNumberFrames * sizeof(float));
list.mBuffers[0].mData = malloc(list.mBuffers[0].mDataByteSize); // Allocate memory for input
// Capture microphone input
if (AudioUnitRender(unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &list) != 0) {
std::cerr << "Failed to render\n";
exit(-1);
}
float* float_data = reinterpret_cast<float*>(list.mBuffers[0].mData);
// Write mic data to file (post-AEC)
char* char_data = reinterpret_cast<char*>(list.mBuffers[0].mData);
file.write(char_data, list.mBuffers[0].mDataByteSize);
// Free the allocated memory
free(list.mBuffers[0].mData);
return noErr;
}
OSStatus outputCallback(void* opaque, AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* inTimeStamp, uint32_t inBusNumber,
uint32_t inNumberFrames, AudioBufferList* ioData) {
static float phase = 0.0f;
float phaseIncrement = kSineFrequency / kSampleRate;
float* outputData = reinterpret_cast<float*>(ioData->mBuffers[0].mData);
for (uint32_t frame = 0; frame < inNumberFrames; ++frame) {
float sineSample = generateSinewave(phase); // Generate 440Hz sinewave
outputData[frame] = sineSample; // Write sinewave to speaker output
phase += phaseIncrement;
if (phase >= 1.0f) phase -= 1.0f; // Keep phase within bounds
}
std::cerr << "Output callback generated sinewave\n";
return noErr;
}
void SetAudioUnitStreamFormat(AudioUnit* unit) {
AudioStreamBasicDescription asbd;
asbd.mSampleRate = kSampleRate;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved;
asbd.mBytesPerPacket = 4;
asbd.mFramesPerPacket = 1;
asbd.mBytesPerFrame = 4;
asbd.mChannelsPerFrame = 1;
asbd.mBitsPerChannel = 32;
if (AudioUnitSetProperty(*unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &asbd, sizeof(asbd)) != 0) {
std::cerr << "Error setting stream format for input stream\n";
exit(-1);
}
if (AudioUnitSetProperty(*unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
1, &asbd, sizeof(asbd)) != 0) {
std::cerr << "Error setting stream format for output stream\n";
exit(-1);
}
}
void EnableAEC(AudioUnit* unit) {
uint32_t enableAEC = 0; // 0 means AEC enabled
if (AudioUnitSetProperty(*unit, kAUVoiceIOProperty_BypassVoiceProcessing,
kAudioUnitScope_Global, 0, &enableAEC, sizeof(enableAEC)) != 0) {
std::cerr << "Error enabling AEC\n";
exit(-1);
}
}
void SetAudioUnitCallbacks(AudioUnit* unit) {
AURenderCallbackStruct inputCallbackStruct = { &inputCallback, unit };
AURenderCallbackStruct outputCallbackStruct = { &outputCallback, unit };
// Set input callback (for bus 1, mic input)
if (AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global,
1, &inputCallbackStruct, sizeof(inputCallbackStruct)) != 0) {
std::cerr << "Error setting input callback\n";
exit(-1);
}
// Set output callback (for bus 0, speaker output)
if (AudioUnitSetProperty(*unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global,
0, &outputCallbackStruct, sizeof(outputCallbackStruct)) != 0) {
std::cerr << "Error setting output callback\n";
exit(-1);
}
}
void BuildVoiceProcessingUnit(AudioUnit* unit) {
AudioComponentDescription desc = {
kAudioUnitType_Output,
kAudioUnitSubType_VoiceProcessingIO,
kAudioUnitManufacturer_Apple,
0, 0
};
AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
if (comp == nullptr) {
std::cerr << "Failed to create AudioComponent\n";
exit(-1);
}
if (AudioComponentInstanceNew(comp, unit) != 0) {
std::cerr << "Failed to create AudioComponentInstance\n";
exit(-1);
}
SetAudioUnitStreamFormat(unit);
EnableAEC(unit); // Enable Acoustic Echo Cancellation (AEC)
SetAudioUnitCallbacks(unit);
}
int main(int argc, const char * argv[]) {
std::cout << "Starting AEC Test with Sinewave Playback...\n";
AudioUnit unit;
BuildVoiceProcessingUnit(&unit);
if (AudioUnitInitialize(unit) != 0) {
std::cerr << "Error initializing AudioUnit\n";
return -1;
}
if (AudioOutputUnitStart(unit) != 0) {
std::cerr << "Error starting AudioUnit\n";
return -1;
}
sleep(10); // Play sinewave and capture audio for 10 seconds
if (AudioOutputUnitStop(unit) != 0) {
std::cerr << "Error stopping AudioUnit\n";
return -1;
}
if (AudioUnitUninitialize(unit) != 0) {
std::cerr << "Error uninitializing AudioUnit\n";
return -1;
}
if (AudioComponentInstanceDispose(unit) != 0) {
std::cerr << "Error disposing AudioUnit\n";
return -1;
}
file.close(); // Close the captured audio file
system("play -r 48000 -t f32 file.f32"); // Play back captured mic audio to verify AEC
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment