1

I use an audio unit with VoiceProcessingIO type to receive voice without echo. In RenderCallback I get sound samples and then set all buffer values to zero so that there is no playback. Now I need to change its sample rate from 48000 to 16000 after receiving the sound and then pass the resulting sound through a lowpass filter.
I can't figure out how to configure several audio units to connect to each other and chain the data.
I know that I have to use kAudioUnitSubType_AUConverter for the converter and kAudioUnitSubType_LowPassFilter for the filter.
I am already desperate to find a help of any kind.

P.S. I found this blog post, there is a similar problem, but the author was never answered to his question. But I don't understand why the author uses two converters. I'm also worried that he uses the Remote type, and I don't understand why he connects these buses and in that order.


public static class SoundSettings 
{
    public static readonly int SampleRate = 16000;
    public static readonly int Channels = 1;
    public static readonly int BytesPerSample = 2;
    public static readonly int FramesPerPacket = 1;
}
private void SetupAudioSession() 
{
    AudioSession.Initialize();
    AudioSession.Category = AudioSessionCategory.PlayAndRecord;
    AudioSession.Mode = AudioSessionMode.GameChat;
    AudioSession.PreferredHardwareIOBufferDuration = 0.08f;
}

private void PrepareAudioUnit() 
{
    _srcFormat = new AudioStreamBasicDescription 
    {
        Format = AudioFormatType.LinearPCM,
        FormatFlags = AudioFormatFlags.LinearPCMIsSignedInteger | 
        AudioFormatFlags.LinearPCMIsPacked,
        SampleRate = AudioSession.CurrentHardwareSampleRate,
        FramesPerPacket = SoundSettings.FramesPerPacket,
        BytesPerFrame = SoundSettings.BytesPerSample * SoundSettings.Channels,
        BytesPerPacket = SoundSettings.FramesPerPacket *
            SoundSettings.BytesPerSample * 
            SoundSettings.Channels,
        BitsPerChannel = SoundSettings.BytesPerSample * 8,
        ChannelsPerFrame = SoundSettings.Channels,
        Reserved = 0
    };

    var audioComponent = AudioComponent.FindComponent(AudioTypeOutput.VoiceProcessingIO);

    _audioUnit = new AudioUnit.AudioUnit(audioComponent);

    _audioUnit.SetEnableIO(true, AudioUnitScopeType.Input, 1);
    _audioUnit.SetEnableIO(true, AudioUnitScopeType.Output, 0);

    _audioUnit.SetFormat(_srcFormat, AudioUnitScopeType.Input, 0);
    _audioUnit.SetFormat(_srcFormat, AudioUnitScopeType.Output, 1);

    _audioUnit.SetRenderCallback(this.RenderCallback, AudioUnitScopeType.Input, 0);
}

private AudioUnitStatus RenderCallback(
    AudioUnitRenderActionFlags actionFlags,
    AudioTimeStamp timeStamp, 
    uint busNumber,
    uint numberFrames, 
    AudioBuffers data)
{
    var status = _audioUnit.Render(ref actionFlags, timeStamp, 1, numberFrames, data);

    if (status != AudioUnitStatus.OK) 
    {
        return status;
    }

    var msgArray = new byte[dataByteSize];
    Marshal.Copy(data[0].Data, msgArray, 0, dataByteSize);

    var msg = _msgFactory.CreateAudioMsg(msgArray, msgArray.Length, (++_lastIndex));
    this.OnMsgReady(msg);

    // Disable playback IO
    var array = new byte[dataByteSize];
    Marshal.Copy(array, 0, data[0].Data, dataByteSize);

    return AudioUnitStatus.NoError;
}
hotpaw2
  • 70,107
  • 14
  • 90
  • 153
Kazus
  • 39
  • 6
  • It looks like you have an sound system that can either process voice or music. First you do not need to change sampling rate you can always add filtering to get lower frequencies. For example to go from 48000 to 16000, you can simply take every third sample. Channing data you want to take the output from one process and put into input of second process and do so in real time. The easiest way is to create a FIFO and use timers. – jdweng Sep 19 '20 at 12:38
  • I implemented a "naive" algorithm to reduce the sample rate. I took the average of three samples. Basically, I can use any interpolation as I'm not sure that the input sample rate will always be 48000. But, since the difference between 48000 and 16000 is more than twice that, i need a filter to avoid sound distortion. In any case this is what this [article](https://www.codeproject.com/Articles/501521/How-to-convert-between-most-audio-formats-in-NET) says, the section "Resampling". In the end, using system capabilities is preferable to self-made algorithms. – Kazus Sep 19 '20 at 13:02
  • Here is list of different formats : https://learn.microsoft.com/en-us/dotnet/api/audiotoolbox.audioformattype?view=xamarin-ios-sdk-12 – jdweng Sep 19 '20 at 21:32

1 Answers1

0

Here a example of a function you can use to connect two audio units (note that source and destination must have the same stream format before you can connect them successfully) :

OSStatus connectAudioUnits(AudioUnit source, AudioUnit destination, AudioUnitElement sourceOutput, AudioUnitElement destinationInput) {
        
        AudioUnitConnection connection;
        connection.sourceAudioUnit    = source;
        connection.sourceOutputNumber = sourceOutput;
        connection.destInputNumber    = destinationInput;
        
        return AudioUnitSetProperty (
                                     destination,
                                     kAudioUnitProperty_MakeConnection,
                                     kAudioUnitScope_Input,
                                     destinationInput,
                                     &connection,
                                     sizeof(connection)
                                     );
    }
dspr
  • 2,383
  • 2
  • 15
  • 19