Search code examples
macoscore-audio

How to enable/disable input or output channels from an aggregate CoreAudio device?


I have thoroughly read the question and answer in this thread: How to exclude input or output channels from an aggregate CoreAudio device?

And it appears to be missing information on the solution: I have created an aggregated device containing multiple audio devices. When calling core audio to receive the number of streams (using kAudioDevicePropertyStreams) the return value is always 1. I have also tried the implementation in CoreAudio Utility classes: CAHALAudioDevice::GetIOProcStreamUsage. Still I could not see how to access sub-streams and disable/enable them as mentioned here. What needs to be done to accomplish disable/enable of sub-streams?

EDIT

Here is CAHALAudioDevice::GetIOProcStreamUsage for reference:

void    CAHALAudioDevice::GetIOProcStreamUsage(AudioDeviceIOProcID 
inIOProcID, bool inIsInput, bool* outStreamUsage) const
{
    //  make an AudioHardwareIOProcStreamUsage the right size
    UInt32 theNumberStreams = GetNumberStreams(inIsInput);
    UInt32 theSize = SizeOf32(void*) + SizeOf32(UInt32) + (theNumberStreams * SizeOf32(UInt32));
    CAAutoFree<AudioHardwareIOProcStreamUsage> theStreamUsage(theSize);

    //  set it up
    theStreamUsage->mIOProc = reinterpret_cast<void*>(inIOProcID);
    theStreamUsage->mNumberStreams = theNumberStreams;

    //  get the property
    CAPropertyAddress theAddress(kAudioDevicePropertyIOProcStreamUsage, inIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput);
    GetPropertyData(theAddress, 0, NULL, theSize, theStreamUsage);

    //  fill out the return value
    for(UInt32 theIndex = 0; theIndex < theNumberStreams; ++theIndex)
    {
        outStreamUsage[theIndex] = (theStreamUsage->mStreamIsOn[theIndex] != 0);
    }
}

Solution

  • For reference, here is the function my program uses the accomplish the results described in the linked question:

    // Tell CoreAudio which input (or output) streams we actually want to use in our device
    // @param devID the CoreAudio audio device ID of the aggregate device to modify
    // @param ioProc the rendering callback-function (as was passed to AudioDeviceCreateIOProcID()'s second argument)
    // @param scope either kAudioObjectPropertyScopeInput or kAudioObjectPropertyScopeOutput depending on which type of channels we want to modify
    // @param numValidChannels how many audio channels in the aggregate device we want to actually use
    // @param rightJustify if true, we want to use the last (numValidChannels) in the device; if false we want to use the first (numValidChannels) in the device
    // @returns 0 on success or -1 on error
    // @note this function doesn't change the layout of the audio-sample data in the audio-render callback; rather it causes some channels of audio in the callback to become zero'd out/unused.
    int SetProcStreamUsage(AudioDeviceID devID, void * ioProc, AudioObjectPropertyScope scope, int numValidChannels, bool rightJustify)
    {  
       const AudioObjectPropertyAddress sizesAddress =
       {                    
           kAudioDevicePropertyStreamConfiguration, 
           scope, 
           kAudioObjectPropertyElementMaster
       };    
       Uint32 streamSizesDataSize = 0;
       OSStatus err = AudioObjectGetPropertyDataSize(devID, &sizesAddress, 0, NULL, &streamSizesDataSize);
       if (err != noErr)
       {        
          printf("SetProcStreamUsage(%u,%i,%i):  AudioObjectGetPropertyDataSize(kAudioDevicePropertyStreamConfiguration) failed!\n", (unsigned int) devID, scope, rightJustify);
          return -1;  // ("AudioObjectGetPropertyDataSize(kAudioDevicePropertyStreamConfiguration) failed");
       }           
                   
       const AudioObjectPropertyAddress usageAddress = 
       {              
           kAudioDevicePropertyIOProcStreamUsage,
           scope,        
           kAudioObjectPropertyElementMaster 
       };                
                            
       Uint32 streamUsageDataSize = 0;
       err = AudioObjectGetPropertyDataSize(devID, &usageAddress, 0, NULL, &streamUsageDataSize);
       if (err != noErr)
       {              
          printf("SetProcStreamUsage(%u,%i,%i):  AudioObjectGetPropertyDataSize(kAudioDevicePropertyIOProcStreamUsage) failed!\n", (unsigned int) devID, scope, rightJustify);
          return -1;  // ("AudioObjectGetPropertyDataSize(kAudioDevicePropertyIOProcStreamUsage) failed");
       }                 
                         
       AudioBufferList * bufList = (AudioBufferList*) malloc(streamSizesDataSize);  // using malloc() because the object-size is variable
       if (bufList)         
       {                 
          int ret;    
          
          err = AudioObjectGetPropertyData(devID, &sizesAddress, 0, NULL, &streamSizesDataSize, bufList);
          if (err == noErr) 
          {           
             AudioHardwareIOProcStreamUsage * streamUsage = (AudioHardwareIOProcStreamUsage *) malloc(streamUsageDataSize);  // using malloc() because the object-size is variable
             if (streamUsage)
             {           
                streamUsage->mIOProc = ioProc;
                err = AudioObjectGetPropertyData(devID, &usageAddress, 0, NULL, &streamUsageDataSize, streamUsage);
                if (err == noErr)
                {  
                   if (bufList->mNumberBuffers == streamUsage->mNumberStreams)
                   {  
                      Int32 numChannelsLeft = numValidChannels;
                      if (rightJustify)
                      {
                         // We only want streams corresponding to the last (N) channels to be enabled
                         for (Int32 i=streamUsage->mNumberStreams-1; i>=0; i--) 
                         {
                            streamUsage->mStreamIsOn[i] = (numChannelsLeft > 0);
                            numChannelsLeft -= bufList->mBuffers[i].mNumberChannels;
                         }
                      }
                      else
                      {
                         // We only want streams corresponding to the first (N) channels to be enabled
                         for (Uint32 i=0; i<streamUsage->mNumberStreams; i++)
                         {
                            streamUsage->mStreamIsOn[i] = (numChannelsLeft > 0);
                            numChannelsLeft -= bufList->mBuffers[i].mNumberChannels;
                         }
                      }
    
                      // Now set the stream-usage per our update, above
                      err = AudioObjectSetPropertyData(devID, &usageAddress, 0, NULL, streamUsageDataSize, streamUsage);
                      if (err != noErr)
                      {
                         printf("SetProcStreamUsage(%u,%i,%i):  AudioObjectSetPropertyData(kAudioDevicePropertyIOProcStreamUsage) failed!\n", (unsigned int) devID, scope, rightJustify);
                         ret = -1;  // ("AudioObjectSetPropertyData(kAudioDevicePropertyIOProcStreamUsage) failed");
                      }
                   }
                   else
                   {
                      printf("SetProcStreamUsage(%u,%i,%i):  #Buffers (%u) doesn't match #Streams (%u)!\n", (unsigned int) devID, scope, rightJustify, bufList->mNumberBuffers, streamUsage->mNumberStreams);
                      ret = -1;
                   }
                }
                else
                {
                   printf("SetProcStreamUsage(%u,%i,%i):  AudioObjectSetPropertyData(kAudioDevicePropertyIOProcStreamUsage) failed!\n", (unsigned int) devID, scope, rightJustify);
                   ret = -1;  // ("AudioObjectGetPropertyData(kAudioDevicePropertyIOProcStreamUsage) failed");
                }
    
                free(streamUsage);
             }
             else ret = -1;  // out of memory?
          }
          else
          {
             printf("SetProcStreamUsage(%u,%i,%i):  AudioObjectGetPropertyData(kAudioDevicePropertyStreamConfiguration) failed!\n", (unsigned int) devID, scope, rightJustify);
             ret = -1;  // ("AudioObjectGetPropertyData(kAudioDevicePropertyStreamConfiguration) failed");
          }
    
          free(bufList);
          return ret;
       }
       else return -1; // out of memory?
    }