AudioIODeviceCallback always on all ports?


#1

Maybe I’m misinterpreting the meaning of the numInput/OutputParameters arguments to AudioDeviceManager::initialise() and AudioDeviceSelectorComponent constructor.

I set both to send me 2 output channels and 0 input channels, but when connected to jack my AudioIODeviceCallback is sent 6 output channels and 2 input channels. These 6/2 correspond to all available system jack in/out ports.

Looking at JackAudioIODevice constructor, it counts all available ports in to totalNumberOfInputChannels and totalNumberOfOutputChannels.
JackAudioIODevice::open() does seem to take the inputChannels/outputChannels parameters into account, so only opens my two outputs and no inputs. But JackAudioIODevice::process() calls the registered audio callback with totalNumberOfInputChannels and totalNumberOfOutputChannels.

Bug or feature? To me it seems quite unnecessary (and annoying) to get a lot of un-asked-for ports sent to the callback.


#2

Could be a bug… I mostly rely on other people to test the JACK stuff, as I don’t really have a linux set-up that I can use for that, so it could be doing something wrong.


#3

I’ll be happy to look into it, but please tell me the expected behaviour: Should the callback be called with only the actually opened ports?


#4

Sorry: yes, the channel array should only contain entries for the channels that are active.


#5

So, here are some suggested changes to juce_linux_JackAudio.cpp

[list]
[]Fix a SIGSEGV when jack server goes away[/]
[]Keep in sync with externally [dis]connected channels (ports), ie changed in qjackqtl or similar[/]
[]Only send active in/out channels to audioDeviceIOCallback[/][/list]

[code]diff --git a/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp
index 65d122b…4d3d5a5 100644
— a/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp
+++ b/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp
@@ -66,7 +66,7 @@ JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client,
JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags));
JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port));
JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port));
-JUCE_DECL_JACK_FUNCTION (int, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg));
+JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback port_connect_callback, void* arg), (client, port_connect_callback, arg));
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id));
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port));
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name));
@@ -106,6 +106,9 @@ namespace

static const char** getJackPorts (jack_client_t* const client, const bool forInput)
{

  • if (client == nullptr)
  •    return nullptr;
    
  • return juce::jack_get_ports (client, nullptr, nullptr,
    forInput ? JackPortIsOutput : JackPortIsInput);
    // (NB: This looks like it’s the wrong way round, but it is correct!)
    @@ -161,8 +164,8 @@ public:
    JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0));
    }
  •        inChans.calloc (totalNumberOfInputChannels + 2);
    
  •        outChans.calloc (totalNumberOfOutputChannels + 2);
    
  •        inputChannelsBuffer.calloc (totalNumberOfInputChannels + 2);
    
  •        outputChannelsBuffer.calloc (totalNumberOfOutputChannels + 2);
       }
    
    }

@@ -216,6 +219,7 @@ public:
close();

     juce::jack_set_process_callback (client, processCallback, this);
  •    juce::jack_set_port_connect_callback (client, portConnectCallback, this);
       juce::jack_on_shutdown (client, shutdownCallback, this);
       juce::jack_activate (client);
       isOpen_ = true;
    

@@ -264,6 +268,8 @@ public:
}
}

  •    updateActivePorts();
    
  •    return lastError;
    
    }

@@ -275,6 +281,7 @@ public:
{
juce::jack_deactivate (client);
juce::jack_set_process_callback (client, processCallback, nullptr);

  •        juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr);
           juce::jack_on_shutdown (client, shutdownCallback, nullptr);
       }
    

@@ -312,27 +319,8 @@ public:
int getCurrentBitDepth() { return 32; }
String getLastError() { return lastError; }

  • BigInteger getActiveOutputChannels() const
  • {
  •    BigInteger outputBits;
    
  •    for (int i = 0; i < outputPorts.size(); i++)
    
  •        if (juce::jack_port_connected ((jack_port_t*) outputPorts [i]))
    
  •            outputBits.setBit (i);
    
  •    return outputBits;
    
  • }
  • BigInteger getActiveInputChannels() const
  • {
  •    BigInteger inputBits;
    
  •    for (int i = 0; i < inputPorts.size(); i++)
    
  •        if (juce::jack_port_connected ((jack_port_t*) inputPorts [i]))
    
  •            inputBits.setBit (i);
    
  •    return inputBits;
    
  • }
  • BigInteger getActiveOutputChannels() const { return activeOutputChannels; }

  • BigInteger getActiveInputChannels() const { return activeInputChannels; }

    int getOutputLatencyInSamples()
    {
    @@ -359,33 +347,37 @@ public:
    private:
    void process (const int numSamples)
    {

  •    int numActiveInChans = 0, numActiveOutChans = 0;
    
  •    int numActiveInputChannels = 0, numActiveOutputChannels = 0;
    
       for (int i = 0; i < totalNumberOfInputChannels; ++i)
       {
    
  •        if (jack_default_audio_sample_t* in
    
  •        if (activeInputChannels[i])
    
  •            if (jack_default_audio_sample_t* in
                   = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), numSamples))
    
  •            inChans [numActiveInChans++] = (float*) in;
    
  •                inputChannelsBuffer [numActiveInputChannels++] = (float*) in;
       }
    
       for (int i = 0; i < totalNumberOfOutputChannels; ++i)
       {
    
  •        if (jack_default_audio_sample_t* out
    
  •        if (activeOutputChannels[i])
    
  •            if (jack_default_audio_sample_t* out
                   = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), numSamples))
    
  •            outChans [numActiveOutChans++] = (float*) out;
    
  •                outputChannelsBuffer [numActiveOutputChannels++] = (float*) out;
       }
    
       const ScopedLock sl (callbackLock);
    
       if (callback != nullptr)
       {
    
  •        callback->audioDeviceIOCallback (const_cast <const float**> (inChans.getData()), numActiveInChans,
    
  •                                         outChans, numActiveOutChans, numSamples);
    
  •        /* check we have any active channels, else it will trigger assertion in juce_AudioSampleBuffer.cpp:72 */
    
  •        if ((numActiveInputChannels + numActiveOutputChannels) > 0)
    
  •            callback->audioDeviceIOCallback (const_cast <const float**> (inputChannelsBuffer.getData()), numActiveInputChannels,
    
  •                                             outputChannelsBuffer, numActiveOutputChannels, numSamples);
       }
       else
       {
    
  •        for (int i = 0; i < numActiveOutChans; ++i)
    
  •            zeromem (outChans[i], sizeof (float) * numSamples);
    
  •        for (int i = 0; i < numActiveOutputChannels; ++i)
    
  •            zeromem (outputChannelsBuffer[i], sizeof (float) * numSamples);
       }
    
    }

@@ -397,6 +389,30 @@ private:
return 0;
}

  • void updateActivePorts()

  • {

  •    /* This function is called on open(), and from jack as callback on external
    
  •     * jack port changes. Jules, is there any risk that this can happen in a
    
  •     * separate thread from the audio thread, meaning we need a critical section?
    
  •     * the below two activeOut/InputChannels are used in process() */
    
  •    activeOutputChannels.clear();
    
  •    activeInputChannels.clear();
    
  •    for (int i = 0; i < outputPorts.size(); i++)
    
  •        if (juce::jack_port_connected ((jack_port_t*) outputPorts [i]))
    
  •            activeOutputChannels.setBit (i);
    
  •    for (int i = 0; i < inputPorts.size(); i++)
    
  •        if (juce::jack_port_connected ((jack_port_t*) inputPorts [i]))
    
  •            activeInputChannels.setBit (i);
    
  • }

  • static void portConnectCallback (jack_port_id_t /* a /, jack_port_id_t / b /, int / isConnect /, void callbackArgument)

  • {

  •    if (callbackArgument != nullptr)
    
  •        ((JackAudioIODevice*) callbackArgument)->updateActivePorts();
    
  • }

  • static void threadInitCallback (void* /* callbackArgument /)
    {
    jack_Log (“JackAudioIODevice::initialise”);
    @@ -424,10 +440,13 @@ private:
    AudioIODeviceCallback
    callback;
    CriticalSection callbackLock;

  • HeapBlock <float*> inChans, outChans;
  • HeapBlock <float*> inputChannelsBuffer, outputChannelsBuffer;
    int totalNumberOfInputChannels;
    int totalNumberOfOutputChannels;
    Array<void*> inputPorts, outputPorts;
  • BigInteger activeInputChannels;
  • BigInteger activeOutputChannels;
    };
    [/code]

#6

Awesome, thanks!


#7

It won’t build with latest tip:

Compiling juce_audio_processors.cpp ../../JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp: In member function ‘void juce::JackAudioIODevice::process(int)’: In file included from ../../JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp:218:0: ../../JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp:371:18: error: ‘numActiveInputChannels’ was not declared in this scope ../../JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp:371:43: error: ‘numActiveOutputChannels’ was not declared in this scope

I had renamed “numActiveInChans” to “numActiveInputChannels” in the patches I sent, since I see no point in abbreviating to save six letters in a name already that long. Anyway, it seems you mixed my new names with the old when applying changes.

[code]diff --git a/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp
index bf9b7a1…13bb02c 100644
— a/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp
+++ b/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp
@@ -368,7 +368,7 @@ private:

     if (callback != nullptr)
     {
  •        if ((numActiveInputChannels + numActiveOutputChannels) > 0)
    
  •        if ((numActiveInChans + numActiveOutChans) > 0)
               callback->audioDeviceIOCallback (const_cast <const float**> (inChans.getData()), numActiveInChans,
                                                outChans, numActiveOutChans, numSamples);
       }
    

[/code]


#8

bump
Jules, please fix as it won’t compile atm.


#9

Sorry, done now. I didn’t change your variable names because I was trying to minimise the diffs and see what was really changing. Should really have checked before committing, but was in a rush…