CoreAudio Mixers and Master volume control

My general experience of CoreAudio on iOS is that it's logically put together and very powerful however woefully lacking in documentation. Even the simplest thing can take a morning of searching the internet to work out. I've decided to blog about the issues I come across to try to help save people time in the future.

In this post I'm going to explain how to set up a mixer to mix a number of CoreAudio channels to one output module. Then, how to change the output volume of the mixer.

Setting up a Mixer

I'm not going to go into the whole process of setting up an AUGraph. If you need to know how to do this you can refer to the tutorial I wrote about CoreAudio and MIDI. The first step in the process is to set up the componentManufacturer, componentType and componentSubType for the new mixer AudioUnit.

  1. // Setup a status object to check for errors
  2. OSStatus result = noErr;
  3. // Create the mixer node
  4. AUNode mixerNode;
  6. // Create a description which will tell CoreAudio what type of unit to create
  7. AudioComponentDescription cd = {};
  8. cd.componentManufacturer = kAudioUnitManufacturer_Apple;
  10. // Define the type and sub type for the mixer - a full list of audio unit types can be found in the AudioUnit.framework/AUComponent.h file
  11. cd.componentType = kAudioUnitType_Mixer;
  12. cd.componentSubType = kAudioUnitSubType_MultiChannelMixer;
  14. // Add the new component to the AUGraph
  15. result = AUGraphAddNode (auGraph, &cd, &mixerNode);
  16. // Check for errors
  17. NSCAssert (result == noErr, @"Unable to add the Mixer unit to the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);

After opening the graph we can get a reference to the unit using the following code:

  1. result = AUGraphNodeInfo (auGraph, mixerNode, 0, &_mixerUnit);
  2. NSCAssert (result == noErr, @"Unable to obtain a reference to the Sampler unit. Error code: %d '%.4s'", (int) result, (const char *)&result);

Here _mixerUnit is an instance variable of type AudioUnit. We need to have this reference in order to add inputs to the mixer as well as changing it's volume.

Next we need to set up the number of input channels we're going to use:

  1. // Define the number of input busses
  2. UInt32 busCount = 2;
  4. // Set the input channels property on the mixer unit
  5. result = AudioUnitSetProperty (
  6. _mixerUnit,
  7. kAudioUnitProperty_ElementCount,
  8. kAudioUnitScope_Input,
  9. 0,
  10. &busCount,
  11. sizeof (busCount)
  12. );
  14. NSCAssert (result == noErr, @"AudioUnitSetProperty Set mixer bus count. Error code: %d '%.4s'", (int) result, (const char *)&result);

Finally we connect our other units to the inputs on the mixer and connect the output of the mixer unit to our remote IO:

  1. // Connect the output bus of an AUNode to one of the inputs of the mixer node
  2. result = AUGraphConnectNodeInput(auGraph, [AUNode to connect], [output channel bus number], mixerNode, [mixer bus number]);
  4. NSCAssert (result == noErr, @"Couldn't connect node to mixer unit output (0) to mixer input (1). Error code: %d '%.4s'", (int) result, (const char *)&result);
  6. // Connect the output of the mixer node to the input of he io node
  7. result = AUGraphConnectNodeInput (auGraph, mixerNode, 0, ioNode, 0);
  8. NSCAssert (result == noErr, @"Unable to interconnect the nodes in the audio processing graph. Error code: %d '%.4s'", (int) result, (const char *)&result);

As in my previous tutorial we connect up the nodes. In this case we attach one node to the mixer and the mixer to the output node. Remember that in iOS a remote io node (output node) is responsible for sending our sound to the speakers or headphones.

The situation can become more complex when using audio from a file because it's necessary to check that the music format is correct at each stage. Since I'm only using MIDI I didn't come across this problem. Finally, I'm going to explain how to use the mixer node to change the AUGraph master volume.

Changing the AUGraph volume

Oddly, IOUnits provide no facility to change the volume of an AUGraph. This means that if you want to control the volume of your AUGraph you need to use a mixer unit even if you only have one channel going in. In the end the code to change the volume is very simple - just one line. Here I'll provide the Objective C function I use to encapsulate the changing of the volume:

  1. -(void) setVolume: (float) volume {
  2. AudioUnitSetParameter(_mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Output, 0, volume, 0);
  3. }

Here, _mixerUnit is the instance variable for the mixer AudioUnit we saved earlier. We provide two parameters to say that we want to change the mixer output volume (it is also possible to change input mixer volumes individually). Next we specify the output bus number - in this case the 0 bus. Then we provide the new volume (0.0 - 1.0) and the delay which is zero.

If you've followed my previous tutorial this should be fairly straightforward and gives you a lot more power because now you can start mixing a whole number of different channels together.


Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.