Created
October 13, 2024 01:12
-
-
Save p-i-/cec37b6c627713849b7bc850a91c74e1 to your computer and use it in GitHub Desktop.
AEC 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
// 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