AUGraph - reconfigure on the fly?

3

I am trying to rearrange the nodes in an AUGraph during playback (render). In particular, I am trying to switch between these two setups:

  1. Multichannel Mixer -> Remote I/O
  2. Multichannel Mixer -> Converter #0 -> Bandpass Filter -> Converter #1 -> Remote I/O

(The stream converters are needed because the bandpass filter uses a floating point format)

Both setups atre tested and work on their own; i.e., if I start with a given setup (node connections), the graph renders properly. But when I try to rearrange the nodes during playback, something goes wrong.

My connection code is this:

void enableBandpassFilter(Boolean enable)
{
    OSStatus result;

    if (enable) {

        // [ A ] Enable

        // Connect mixer to converter0
        result = AUGraphConnectNodeInput(processingGraph,       // (in) graph
                                         mixerNode,             // (in) src node
                                         0,                     // (in) src output number
                                         converterNode0,        // (in) dst node
                                         0);                    // (in) dst input number
        if ( result != noErr ){
            DLog(@"AUGraphConnectNodeInput() Failed for mixer->converter0");
        }

        // Connect converter0 to bandpass
        result = AUGraphConnectNodeInput(processingGraph,       // (in) graph
                                         converterNode0,        // (in) src node
                                         0,                     // (in) src output number
                                         bandpassNode,          // (in) dst node
                                         0);                    // (in) dst input number
        if ( result != noErr ){
            DLog(@"AUGraphConnectNodeInput() Failed for converter0->bandpass");
        }

        // Connect bandpass to converter1
        result = AUGraphConnectNodeInput(processingGraph,       // (in) graph
                                         bandpassNode,          // (in) src node
                                         0,                     // (in) src output number
                                         converterNode1,        // (in) dst node
                                         0);                    // (in) dst input number
        if ( result != noErr ){
            DLog(@"AUGraphConnectNodeInput() Failed for bandpass->converter1");
        }

        // Connect converter1 to i/o
        result = AUGraphConnectNodeInput(processingGraph,
                                         converterNode1,
                                         0,
                                         remoteIONode,
                                         0);
        if ( result != noErr ){
            DLog(@"AUGraphConnectNodeInput() Failed for converter1->output");
        }
    }
    else{
        // [ B ] Disable


        result = AUGraphDisconnectNodeInput(processingGraph,
                                            remoteIONode,
                                            0);
        if ( result != noErr ){
            DLog(@"AUGraphDisconnectNodeInput() Failed for output");
        }

        // Connect mixer to remote I/O
        result = AUGraphConnectNodeInput(processingGraph,       // (in) graph
                                         mixerNode,             // (in) src node
                                         0,                     // (in) src output number
                                         remoteIONode,          // (in) dst node
                                         0);                    // (in) dst input number
        if ( result != noErr ){
            DLog(@"AUGraphConnectNodeInput() Failed for mixer->output");
        }
    }
}

(The graph and nodes are file-scope globals. This C function is defined in the same file as my Obj-C sound manager class). I use this same function both at graph initialization and afterwards for switching.

I've tried two approaches:

  • Calling the enableBandpassFilter() right away (from within an Obj-C method), and

  • Register a notification callback with AUGraphAddRenderNotify() (which runs in the 'real time priority thread') and calling enableBandpassFilter() from within the callback.

Both approaches fail miserably. To make thing simple and avoid the pitfall of disconnected audio units not being initialized when the graph starts, I start with the 5 node configuration #2 above (bandpass filter: on) and from there try to switch to the bypass configuration #1 above (bandpass filter: off).

On startup, the audio graph looks like this:

AudioUnitGraph 0x1E9B000:
  Member Nodes:
    node 1: 'aumx' 'mcmx' 'appl', instance 0x170820fe0 O  
    node 2: 'aufc' 'conv' 'appl', instance 0x178424040 O  
    node 3: 'aufx' 'bpas' 'appl', instance 0x1784241a0 O  
    node 4: 'aufc' 'conv' 'appl', instance 0x1708295a0 O  
    node 5: 'auou' 'rioc' 'appl', instance 0x17082b020 O  
  Connections:
    node   1 bus   0 => node   2 bus   0  [ 2 ch,  44100 Hz, 'lpcm' (0x00000C2C) 8.24-bit little-endian signed integer, deinterleaved]
    node   2 bus   0 => node   3 bus   0  [ 2 ch,  44100 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]
    node   3 bus   0 => node   4 bus   0  [ 2 ch,  44100 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]
    node   4 bus   0 => node   5 bus   0  [ 2 ch,      0 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]
  Input Callbacks:
    {0x10000d17c, 0x10004ea90} => node   1 bus   0  [2 ch, 44100 Hz]
    {0x10000d17c, 0x10004ea90} => node   1 bus   1  [2 ch, 44100 Hz]
    {0x10000d17c, 0x10004ea90} => node   1 bus   2  [2 ch, 44100 Hz]
    {0x10000d17c, 0x10004ea90} => node   1 bus   3  [2 ch, 44100 Hz]
  CurrentState:
    mLastUpdateError=0, eventsToProcess=F, isInitialized=F, isRunning=F

(The "0Hz" part in the last connection is a bit alarming, but sound flows properly at this point)

When I try to bypass the filter using callbacks, I get the dreaded -10861 error (kAUGraphErr_InvalidConnection) when connection the mixer to the output.

When I try to bypass the filter on the main thread (Obj-C method), I get no error result codes but the filter is not deactivated.

In both cases, after the switch the graph log becomes this:

AudioUnitGraph 0x1EA2000:
  Member Nodes:
    node 1: 'aumx' 'mcmx' 'appl', instance 0x178228d80 O I
    node 2: 'aufc' 'conv' 'appl', instance 0x17062c760 O I
    node 3: 'aufx' 'bpas' 'appl', instance 0x17062dfe0 O I
    node 4: 'aufc' 'conv' 'appl', instance 0x170636d00 O I
    node 5: 'auou' 'rioc' 'appl', instance 0x170637200 O I
  Connections:
    node   1 bus   0 => node   2 bus   0  [ 2 ch,  44100 Hz, 'lpcm' (0x00000C2C) 8.24-bit little-endian signed integer, deinterleaved]
    node   2 bus   0 => node   3 bus   0  [ 2 ch,  44100 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]
    node   3 bus   0 => node   4 bus   0  [ 2 ch,  44100 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]
  Input Callbacks:
    {0x1000452fc, 0x100086a40} => node   1 bus   0  [2 ch, 44100 Hz]
    {0x1000452fc, 0x100086a40} => node   1 bus   1  [2 ch, 44100 Hz]
    {0x1000452fc, 0x100086a40} => node   1 bus   2  [2 ch, 44100 Hz]
    {0x1000452fc, 0x100086a40} => node   1 bus   3  [2 ch, 44100 Hz]
  Problem Events when updated:
    connect:source=1,bus=0,dest=5,bus=0
  CurrentState:
    mLastUpdateError=0, eventsToProcess=F, isInitialized=T, isRunning=T (1)

(Notice the Problem Events when updated: part detailing the impossibility to connect Multichannel Mixer and Remote I/O. Also, the O after each node changes into O I.)

I can't believe the nodes are incompatible, because if I start form that configuration, it works.

So, what is the proper way to reconnect nodes on the fly? What am I missing?

*Sorry for the long question.

EDIT (Solution?): As I mentioned in the comments, it turns out the graph rewiring must be done in the main thread, and not in the Core Audio thread (the relevant objects are locked in that context). I moved the rewiring code to the main thread (instance method), and also added a call to AUGraphClearConnections() before any call to AUGraphConnectNodeInput(), and now it works, even if I don't stop/restart the graph.

The only problem right now is, whenever I rewire, all sounds playing stop (render callbacks stop being called) until I stop/start those sounds again. I must inspect each bus of the mixer to see if the callback is attached, bus enabled, etc... I'll do just that right now.

ios
core-audio
audiounit
asked on Stack Overflow Apr 13, 2014 by Nicolas Miari • edited Apr 13, 2014 by Nicolas Miari

1 Answer

5

It turns out the graph rewiring must be done in the main thread, and not in the Core Audio thread: the relevant objects are locked in that context (some of the -admittedly fragmented, sometimes contradictory?- Core Audio official documentation seemed to suggest you can do it from any context, because AUGraph is thread safe and takes care of everything).

So I moved the rewiring code to an instance method of my class (which runs on the main thread), and also added a call to AUGraphClearConnections() before any call to AUGraphConnectNodeInput(), and now it works, even if I don't stop/restart the graph.

So what I do is (Main thread):

  1. Call AUGraphClearConnections() (disconnects everything).
  2. Make all connections (new nodes and existing ones, full graph in its definitive form). This includes nodes as well as input render callbacks.
  3. Call AUGRaphUpdate() in synchronous mode (pass NULL to second parameter).

This does the trick.

answered on Stack Overflow Apr 18, 2014 by Nicolas Miari

User contributions licensed under CC BY-SA 3.0