Created
October 12, 2024 16:36
-
-
Save p-i-/cfdfba223b4a758dd2b97b06438fa839 to your computer and use it in GitHub Desktop.
Broken: kAudioUnitSubType_VoiceProcessingIO / macOS
This file contains 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
#include "MainComponent.h" | |
#include <AudioToolbox/AudioToolbox.h> | |
#include <CoreAudio/CoreAudio.h> | |
#include <AudioUnit/AudioUnit.h> | |
#include <cmath> | |
// SineWaveGenerator methods | |
SineWaveGenerator::SineWaveGenerator() : currentAngle(0.0), angleDelta(0.0) {} | |
void SineWaveGenerator::setFrequency(float frequency, double sampleRate) | |
{ | |
angleDelta = 2.0 * M_PI * frequency / sampleRate; | |
} | |
float SineWaveGenerator::getNextSample() | |
{ | |
auto sample = std::sin(currentAngle); | |
currentAngle += angleDelta; | |
if (currentAngle > 2.0 * M_PI) | |
currentAngle -= 2.0 * M_PI; | |
return sample; | |
} | |
// MainComponent constructor | |
MainComponent::MainComponent() : currentSampleRate(0.0), audioUnit(nullptr) | |
{ | |
setSize(400, 300); | |
setAudioChannels(1, 2); // Mono input, stereo output | |
micSamples.reserve(maxSamples); // Reserve space for 15s of audio | |
if (initVoiceProcessingIO()) { | |
DBG("Audio Unit initialized and started successfully."); | |
} else { | |
DBG("Failed to initialize Audio Unit."); | |
} | |
} | |
// Destructor | |
MainComponent::~MainComponent() | |
{ | |
shutdownAudio(); | |
if (audioUnit) { | |
AudioOutputUnitStop(audioUnit); | |
AudioUnitUninitialize(audioUnit); | |
AudioComponentInstanceDispose(audioUnit); | |
} | |
} | |
// Prepare to play | |
void MainComponent::prepareToPlay(int samplesPerBlockExpected, double sampleRate) | |
{ | |
currentSampleRate = sampleRate; | |
sineWaveGenerator.setFrequency(440.0f, sampleRate); // Set frequency to 440Hz | |
} | |
// Audio Unit render callback function | |
static OSStatus audioUnitRenderCallback(void* inRefCon, | |
AudioUnitRenderActionFlags* ioActionFlags, | |
const AudioTimeStamp* inTimeStamp, | |
UInt32 inBusNumber, | |
UInt32 inNumberFrames, | |
AudioBufferList* ioData) | |
{ | |
MainComponent* self = static_cast<MainComponent*>(inRefCon); | |
// Debug message for callback hit | |
//DBG("RENDER CALLBACK - inBusNumber: " + juce::String(inBusNumber) + ", inNumberFrames: " + juce::String(inNumberFrames)); | |
// ^ inBusNumber is always 0, inNumberFrames is 470 or 471 | |
// Retrieve the microphone input (from bus 1) | |
if (inBusNumber == 1) | |
{ | |
AudioBufferList bufferList; | |
bufferList.mNumberBuffers = 1; | |
bufferList.mBuffers[0].mNumberChannels = 1; | |
bufferList.mBuffers[0].mDataByteSize = inNumberFrames * sizeof(float); | |
bufferList.mBuffers[0].mData = malloc(inNumberFrames * sizeof(float)); // Allocate memory for input | |
// Capture the mic input from the audio unit | |
OSStatus status = AudioUnitRender(self->audioUnit, | |
ioActionFlags, | |
inTimeStamp, | |
1, // Bus 1 is for mic input | |
inNumberFrames, | |
&bufferList); | |
// Add debug log for AudioUnitRender status | |
DBG("AudioUnitRender status: " << status); | |
if (status == noErr) | |
{ | |
// Copy mic input samples to the micSamples buffer | |
float* micData = static_cast<float*>(bufferList.mBuffers[0].mData); | |
DBG("Capturing mic data. Number of frames: " << juce::String(inNumberFrames)); | |
self->micSamples.insert(self->micSamples.end(), micData, micData + inNumberFrames); | |
// Debug log for checking micSamples buffer size | |
DBG("micSamples size after capture: " << self->micSamples.size()); | |
} | |
else | |
{ | |
DBG("AudioUnitRender failed with status: " << status); | |
} | |
free(bufferList.mBuffers[0].mData); // Free the allocated memory | |
} | |
// Output to speakers (from bus 0) | |
if (inBusNumber == 0) | |
{ | |
float* outLeft = static_cast<float*>(ioData->mBuffers[0].mData); | |
float* outRight = static_cast<float*>(ioData->mBuffers[1].mData); | |
for (UInt32 frame = 0; frame < inNumberFrames; ++frame) | |
{ | |
float sineSample = self->sineWaveGenerator.getNextSample(); | |
outLeft[frame] = sineSample; | |
outRight[frame] = sineSample; | |
} | |
} | |
return noErr; | |
} | |
void MainComponent::getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill) | |
{ | |
// Capture mic input and check the size | |
// DBG("Current micSamples size: " << micSamples.size()); // always 0 | |
if (micSamples.size() >= maxSamples) | |
{ | |
saveToWav(); | |
juce::JUCEApplication::quit(); | |
} | |
} | |
void MainComponent::saveToWav() | |
{ | |
// File location for saving | |
juce::File file(juce::File::getSpecialLocation(juce::File::userDesktopDirectory).getChildFile("recorded_audio.wav")); | |
// Check if file exists and delete it if necessary | |
if (file.existsAsFile()) | |
{ | |
file.deleteFile(); // Delete existing file | |
DBG("Existing file deleted."); | |
} | |
juce::WavAudioFormat wavFormat; | |
std::unique_ptr<juce::AudioFormatWriter> writer (wavFormat.createWriterFor(new juce::FileOutputStream(file), | |
currentSampleRate, | |
1, // Mono | |
24, // Bits per sample | |
{}, // No metadata | |
0)); | |
if (writer != nullptr) | |
{ | |
const float* channels[] = { micSamples.data() }; // Create an array of pointers | |
writer->writeFromFloatArrays(channels, 1, micSamples.size()); // Pass the array of channels | |
DBG("Audio saved to " + file.getFullPathName()); | |
} | |
else | |
{ | |
DBG("Failed to save WAV."); | |
} | |
} | |
// Release resources (JUCE method) | |
void MainComponent::releaseResources() | |
{ | |
} | |
// Paint (required override) | |
void MainComponent::paint(juce::Graphics& g) | |
{ | |
g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId)); | |
g.setColour(juce::Colours::white); | |
g.setFont(15.0f); | |
g.drawFittedText("VoiceProcessingIO Test", getLocalBounds(), juce::Justification::centred, 1); | |
} | |
// Resized (required override) | |
void MainComponent::resized() | |
{ | |
} | |
bool MainComponent::initVoiceProcessingIO() | |
{ | |
AudioComponentDescription desc; | |
desc.componentType = kAudioUnitType_Output; | |
desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO; | |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; | |
desc.componentFlags = 0; | |
desc.componentFlagsMask = 0; | |
AudioComponent component = AudioComponentFindNext(nullptr, &desc); | |
if (!component) { | |
DBG("Failed to find VoiceProcessingIO component."); | |
return false; | |
} | |
OSStatus status = AudioComponentInstanceNew(component, &audioUnit); | |
if (status != noErr) { | |
DBG("Failed to create Audio Unit instance. Status: " << status); | |
return false; | |
} | |
// Disable AEC bypass to enable voice processing | |
UInt32 bypassAEC = 0; | |
status = AudioUnitSetProperty(audioUnit, | |
kAUVoiceIOProperty_BypassVoiceProcessing, | |
kAudioUnitScope_Global, | |
0, | |
&bypassAEC, | |
sizeof(bypassAEC)); | |
if (status != noErr) { | |
DBG("Failed to set AEC property. Status: " << status); | |
return false; | |
} | |
// Set up render callback for output (bus 0) | |
AURenderCallbackStruct outputCallback; | |
outputCallback.inputProc = audioUnitRenderCallback; | |
outputCallback.inputProcRefCon = this; | |
status = AudioUnitSetProperty(audioUnit, | |
kAudioUnitProperty_SetRenderCallback, | |
kAudioUnitScope_Input, // Input scope for output (bus 0 sends to speaker) | |
0, // Bus 0 for speaker output | |
&outputCallback, | |
sizeof(outputCallback)); | |
if (status != noErr) { | |
DBG("Failed to set render callback for output. Status: " << status); | |
return false; | |
} | |
// Set up render callback for input (bus 1) | |
AURenderCallbackStruct inputCallback; | |
inputCallback.inputProc = audioUnitRenderCallback; | |
inputCallback.inputProcRefCon = this; | |
status = AudioUnitSetProperty(audioUnit, | |
kAudioUnitProperty_SetRenderCallback, | |
kAudioUnitScope_Global, // Global scope for input | |
1, // Bus 1 for mic input | |
&inputCallback, | |
sizeof(inputCallback)); | |
if (status != noErr) { | |
DBG("Failed to set render callback for input. Status: " << status); | |
return false; | |
} | |
// Initialize and start the audio unit | |
status = AudioUnitInitialize(audioUnit); | |
if (status != noErr) { | |
DBG("Failed to initialize Audio Unit. Status: " << status); | |
return false; | |
} | |
status = AudioOutputUnitStart(audioUnit); | |
if (status != noErr) { | |
DBG("Failed to start Audio Unit. Status: " << status); | |
return false; | |
} | |
return true; | |
} |
This file contains 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
#pragma once | |
#include <JuceHeader.h> | |
#include <vector> | |
class SineWaveGenerator | |
{ | |
public: | |
SineWaveGenerator(); | |
void setFrequency(float frequency, double sampleRate); | |
float getNextSample(); | |
private: | |
double currentAngle = 0.0, angleDelta = 0.0; | |
}; | |
class MainComponent : public juce::AudioAppComponent | |
{ | |
public: | |
MainComponent(); | |
~MainComponent() override; | |
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; | |
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override; | |
void releaseResources() override; | |
void paint (juce::Graphics&) override; | |
void resized() override; | |
bool initVoiceProcessingIO(); | |
void stopAudioUnit(); | |
void saveToWav(); | |
// Make these members public so the callback function can access them | |
public: | |
AudioUnit audioUnit = nullptr; | |
SineWaveGenerator sineWaveGenerator; | |
std::vector<float> micSamples; // Buffer for mic input samples | |
double currentSampleRate = 0.0; | |
int maxSamples = 5 * 48000; // Capacity for 5 seconds at 48000Hz | |
private: | |
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent) | |
}; |
This file contains 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
JUCE v8.0.2 | |
AddInstanceForFactory: No factory registered for id <CFUUID 0x60000183fbc0> F8BB1C28-BAE8-11D6-9C31-00039315CD46 | |
vpPlatformUtil.mm:312 Cannot retrieve theDeviceBoardID string... | |
vpPlatformUtil.mm:312 Cannot retrieve theDeviceBoardID string... | |
66307 HALC_ProxyIOContext.cpp:1354 HALC_ProxyIOContext::IOWorkLoop: context 13862 received an out of order message (got -1 want: 11) | |
AUVPAggregate.cpp:2567 AggInpStreamsChanged wait failed | |
AUVoiceIO can't set vp bypass state for null bundleID | |
KeystrokeSuppressorCore.cpp:44 ERROR: KeystrokeSuppressor initialization was unsuccessful. Invalid or no plist was provided. AU will be bypassed. | |
vpStrategyManager.mm:486 Error code 2003332927 reported at GetPropertyInfo | |
Audio Unit initialized and started successfully. | |
Message from debugger: killed |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment