Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Last active June 5, 2020 09:52
Show Gist options
  • Save ChunMinChang/ea74c8228745449873716e1d98ba956e to your computer and use it in GitHub Desktop.
Save ChunMinChang/ea74c8228745449873716e1d98ba956e to your computer and use it in GitHub Desktop.
CoreAudio Playground #coreaudio #multichannel_audio

TODO

  • Get channel layout when layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions
    • Need to figure out when the condition is true first
    • Maybe it depends on some specific hardwares
  • Find out why getting/setting channel layout does not work on input side
  • Add a verifyNoErr function to process all if(r != noErr)
  • Write some sample code for kAudioOutputUnitProperty_ChannelMap
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/CoreAudio.h>
#define kAudioUnitOutputBus 0
#define kAudioUnitInputBus 1
#define DEBUG true // Set true to log the debugging messages.
#define LOG(...) DEBUG && fprintf(stdout, __VA_ARGS__)
#define fieldOffset(type, field) ((size_t) &((type *) 0)->field)
/* SMPTE channel layout (also known as wave order)
*
* DUAL-MONO L R
* DUAL-MONO-LFE L R LFE
* MONO M
* MONO-LFE M LFE
* STEREO L R
* STEREO-LFE L R LFE
* 3F L R C
* 3F-LFE L R C LFE
* 2F1 L R S
* 2F1-LFE L R LFE S
* 3F1 L R C S
* 3F1-LFE L R C LFE S
* 2F2 L R LS RS
* 2F2-LFE L R LFE LS RS
* 3F2 L R C LS RS
* 3F2-LFE L R C LFE LS RS
* 3F3R-LFE L R C LFE RC LS RS
* 3F4-LFE L R C LFE RLS RRS LS RS
*
* The abbreviation of channel name is defined in following table:
* Abbr Channel name
* ---------------------------
* M Mono
* L Left
* R Right
* C Center
* LS Left Surround
* RS Right Surround
* RLS Rear Left Surround
* RC Rear Center
* RRS Rear Right Surround
* LFE Low Frequency Effects
*/
enum Layout {
SMPTE_UNDEFINED, // Indicate the speaker's layout is undefined.
SMPTE_DUAL_MONO,
SMPTE_DUAL_MONO_LFE,
SMPTE_MONO,
SMPTE_MONO_LFE,
SMPTE_STEREO,
SMPTE_STEREO_LFE,
SMPTE_3F,
SMPTE_3F_LFE,
SMPTE_2F1,
SMPTE_2F1_LFE,
SMPTE_3F1,
SMPTE_3F1_LFE,
SMPTE_2F2,
SMPTE_2F2_LFE,
SMPTE_3F2,
SMPTE_3F2_LFE,
SMPTE_3F3R_LFE,
SMPTE_3F4_LFE,
SMPTE_MAX
};
typedef struct {
const char* name;
const unsigned int channels;
const Layout layout;
} LayoutInfo;
const LayoutInfo LAYOUT_INFO[SMPTE_MAX] = {
{ "undefined", 0, SMPTE_UNDEFINED },
{ "dual mono", 2, SMPTE_DUAL_MONO },
{ "dual mono lfe", 3, SMPTE_DUAL_MONO_LFE },
{ "mono", 1, SMPTE_MONO },
{ "mono lfe", 2, SMPTE_MONO_LFE },
{ "stereo", 2, SMPTE_STEREO },
{ "stereo lfe", 3, SMPTE_STEREO_LFE },
{ "3f", 3, SMPTE_3F },
{ "3f lfe", 4, SMPTE_3F_LFE },
{ "2f1", 3, SMPTE_2F1 },
{ "2f1 lfe", 4, SMPTE_2F1_LFE },
{ "3f1", 4, SMPTE_3F1 },
{ "3f1 lfe", 5, SMPTE_3F1_LFE },
{ "2f2", 4, SMPTE_2F2 },
{ "2f2 lfe", 5, SMPTE_2F2_LFE },
{ "3f2", 5, SMPTE_3F2 },
{ "3f2 lfe", 6, SMPTE_3F2_LFE },
{ "3f3r lfe", 7, SMPTE_3F3R_LFE },
{ "3f4 lfe", 8, SMPTE_3F4_LFE }
};
enum Channel {
CHANNEL_INVALID = -1,
CHANNEL_MONO = 0,
CHANNEL_LEFT,
CHANNEL_RIGHT,
CHANNEL_CENTER,
CHANNEL_LS,
CHANNEL_RS,
CHANNEL_RLS,
CHANNEL_RCENTER,
CHANNEL_RRS,
CHANNEL_LFE,
CHANNEL_MAX // Max number of supported channels.
};
const Channel CHANNEL_INDEX_TO_ORDER[SMPTE_MAX][CHANNEL_MAX] = {
{ CHANNEL_INVALID }, // UNDEFINED
{ CHANNEL_LEFT, CHANNEL_RIGHT }, // DUAL_MONO
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE }, // DUAL_MONO_LFE
{ CHANNEL_MONO }, // MONO
{ CHANNEL_MONO, CHANNEL_LFE }, // MONO_LFE
{ CHANNEL_LEFT, CHANNEL_RIGHT }, // STEREO
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE }, // STEREO_LFE
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER }, // 3F
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE }, // 3F_LFE
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_RCENTER }, // 2F1
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_RCENTER }, // 2F1_LFE
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_RCENTER }, // 3F1
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER }, // 3F1_LFE
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LS, CHANNEL_RS }, // 2F2
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS }, // 2F2_LFE
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LS, CHANNEL_RS }, // 3F2
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS }, // 3F2_LFE
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER, CHANNEL_LS, CHANNEL_RS }, // 3F3R_LFE
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RLS, CHANNEL_RRS, CHANNEL_LS, CHANNEL_RS } // 3F4_LFE
};
// DUAL_MONO(_LFE) is same as STEREO(_LFE).
#define MASK_MONO (1 << CHANNEL_MONO)
#define MASK_MONO_LFE (MASK_MONO | (1 << CHANNEL_LFE))
#define MASK_STEREO ((1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT))
#define MASK_STEREO_LFE (MASK_STEREO | (1 << CHANNEL_LFE))
#define MASK_3F (MASK_STEREO | (1 << CHANNEL_CENTER))
#define MASK_3F_LFE (MASK_3F | (1 << CHANNEL_LFE))
#define MASK_2F1 (MASK_STEREO | (1 << CHANNEL_RCENTER))
#define MASK_2F1_LFE (MASK_2F1 | (1 << CHANNEL_LFE))
#define MASK_3F1 (MASK_3F | (1 << CHANNEL_RCENTER))
#define MASK_3F1_LFE (MASK_3F1 | (1 << CHANNEL_LFE))
#define MASK_2F2 (MASK_STEREO | (1 << CHANNEL_LS) | (1 << CHANNEL_RS))
#define MASK_2F2_LFE (MASK_2F2 | (1 << CHANNEL_LFE))
#define MASK_3F2 (MASK_2F2 | (1 << CHANNEL_CENTER))
#define MASK_3F2_LFE (MASK_3F2 | (1 << CHANNEL_LFE))
#define MASK_3F3R_LFE (MASK_3F2_LFE | (1 << CHANNEL_RCENTER))
#define MASK_3F4_LFE (MASK_3F2_LFE | (1 << CHANNEL_RLS) | (1 << CHANNEL_RRS))
enum Side {
INPUT,
OUTPUT
};
struct AudioDevice
{
AudioDeviceID id;
AudioUnit unit;
Layout layout;
};
std::unique_ptr<AudioChannelLayout, decltype(&free)>
AllocAudioChannelLayout(size_t sz)
{
assert(sz >= sizeof(AudioChannelLayout));
AudioChannelLayout * acl = reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));
assert(acl); // Assert the allocation works.
return std::unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free);
}
Channel
LabelToChannel(UInt32 label)
{
switch (label) {
case kAudioChannelLabel_Mono: return CHANNEL_MONO;
case kAudioChannelLabel_Left: return CHANNEL_LEFT;
case kAudioChannelLabel_Right: return CHANNEL_RIGHT;
case kAudioChannelLabel_Center: return CHANNEL_CENTER;
case kAudioChannelLabel_LFEScreen: return CHANNEL_LFE;
case kAudioChannelLabel_LeftSurround: return CHANNEL_LS;
case kAudioChannelLabel_RightSurround: return CHANNEL_RS;
case kAudioChannelLabel_RearSurroundLeft: return CHANNEL_RLS;
case kAudioChannelLabel_RearSurroundRight: return CHANNEL_RRS;
case kAudioChannelLabel_CenterSurround: return CHANNEL_RCENTER;
default: return CHANNEL_INVALID;
}
}
AudioChannelLabel
ChannelToLabel(Channel channel)
{
switch (channel) {
case CHANNEL_MONO: return kAudioChannelLabel_Mono;
case CHANNEL_LEFT: return kAudioChannelLabel_Left;
case CHANNEL_RIGHT: return kAudioChannelLabel_Right;
case CHANNEL_CENTER: return kAudioChannelLabel_Center;
case CHANNEL_LFE: return kAudioChannelLabel_LFEScreen;
case CHANNEL_LS: return kAudioChannelLabel_LeftSurround;
case CHANNEL_RS: return kAudioChannelLabel_RightSurround;
case CHANNEL_RLS: return kAudioChannelLabel_RearSurroundLeft;
case CHANNEL_RRS: return kAudioChannelLabel_RearSurroundRight;
case CHANNEL_RCENTER: return kAudioChannelLabel_CenterSurround;
default: return kAudioChannelLabel_Unknown;
}
}
Layout
ConvertToSMPTE(AudioChannelLayout* layout)
{
if (layout->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions) {
// kAudioChannelLayoutTag_UseChannelBitmap
// kAudioChannelLayoutTag_Mono
// kAudioChannelLayoutTag_Stereo
// ....
LOG("We only handle UseChannelDescriptions for now.\n");
return SMPTE_UNDEFINED;
}
UInt32 mask = 0;
for (UInt32 i = 0 ; i < layout->mNumberChannelDescriptions ; ++i) {
Channel c = LabelToChannel(layout->mChannelDescriptions[i].mChannelLabel);
if (c == CHANNEL_INVALID) {
return SMPTE_UNDEFINED;
}
mask |= 1 << c;
}
switch(mask) {
case MASK_MONO: return SMPTE_MONO;
case MASK_MONO_LFE: return SMPTE_MONO_LFE;
case MASK_STEREO: return SMPTE_STEREO;
case MASK_STEREO_LFE: return SMPTE_STEREO_LFE;
case MASK_3F: return SMPTE_3F;
case MASK_3F_LFE: return SMPTE_3F_LFE;
case MASK_2F1: return SMPTE_2F1;
case MASK_2F1_LFE: return SMPTE_2F1_LFE;
case MASK_3F1: return SMPTE_3F1;
case MASK_3F1_LFE: return SMPTE_3F1_LFE;
case MASK_2F2: return SMPTE_2F2;
case MASK_2F2_LFE: return SMPTE_2F2_LFE;
case MASK_3F2: return SMPTE_3F2;
case MASK_3F2_LFE: return SMPTE_3F2_LFE;
case MASK_3F3R_LFE: return SMPTE_3F3R_LFE;
case MASK_3F4_LFE: return SMPTE_3F4_LFE;
default: return SMPTE_UNDEFINED;
}
}
AudioDeviceID
GetDefaultDeviceId(Side side)
{
UInt32 size;
OSStatus r;
AudioDeviceID id;
AudioObjectPropertyAddress address = {
side == INPUT ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
size = sizeof(id);
r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&address,
0,
NULL,
&size,
&id);
if (r != noErr) {
LOG("%d: Could not get %s device id\n", id, side == INPUT ? "input" : "output");
return kAudioObjectUnknown;
}
LOG("Get default %s device: %d\n", side == INPUT ? "input" : "output", id);
return id;
}
Layout
GetPreferredChannelLayout(Side side)
{
OSStatus r = noErr;
UInt32 size = 0;
AudioDeviceID id = GetDefaultDeviceId(side);
AudioObjectPropertyAddress adr = { kAudioDevicePropertyPreferredChannelLayout,
kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMaster };
r = AudioObjectGetPropertyDataSize(id/*kAudioObjectSystemObject*/, &adr, 0, NULL, &size);
if (r != noErr) {
LOG("%d: Could not get size of kAudioDevicePropertyPreferredChannelLayout.\n", r);
return SMPTE_UNDEFINED;
}
assert(size > 0);
auto layout = AllocAudioChannelLayout(size);
r = AudioObjectGetPropertyData(id, &adr, 0, NULL, &size, layout.get());
if (r != noErr) {
LOG("%d: Could not get kAudioDevicePropertyPreferredChannelLayout.\n", r);
return SMPTE_UNDEFINED;
}
return ConvertToSMPTE(layout.get());
}
AudioDeviceID
GetCurrentDeviceId(AudioUnit& unit, Side side)
{
OSStatus r = noErr;
AudioDeviceID id;
UInt32 size = sizeof(id);
r = AudioUnitGetProperty(unit,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
side == INPUT ? kAudioUnitInputBus : kAudioUnitOutputBus,
&id,
&size);
if (r != noErr) {
LOG("%d: Could not get id of current audio device.\n", r);
return kAudioObjectUnknown;
}
LOG("Get current %s device: %d\n", side == INPUT ? "input" : "output", id);
return id;
}
// id = 0 for default device
bool
GetAudioUnit(AudioUnit& unit, Side side, AudioDeviceID id)
{
AudioComponentDescription desc;
AudioComponent comp;
UInt32 size;
OSStatus r = noErr;
UInt32 hasIO = 0;
UInt32 enable = 0;
desc.componentType = kAudioUnitType_Output;
// Use kAudioUnitSubType_RemoteIO for iPhone
// desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentSubType = kAudioUnitSubType_HALOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
comp = AudioComponentFindNext(NULL, &desc);
if (comp == NULL) {
LOG("Could not find matching audio hardware.\n");
return false;
}
r = AudioComponentInstanceNew(comp, &unit);
if (r != noErr) {
LOG("%d: Could not get unit by AudioComponentInstanceNew\n", r);
return false;
}
enable = 1;
r = AudioUnitSetProperty(unit,
kAudioOutputUnitProperty_EnableIO,
side == INPUT ? kAudioUnitScope_Input : kAudioUnitScope_Output,
side == INPUT ? kAudioUnitInputBus : kAudioUnitOutputBus,
&enable,
sizeof(enable));
if (r != noErr) {
LOG("%d: Could not set IO for %s device\n", r, side == INPUT ? "input" : "output");
return false;
}
enable = 0;
r = AudioUnitSetProperty(unit,
kAudioOutputUnitProperty_EnableIO,
side == INPUT ? kAudioUnitScope_Output : kAudioUnitScope_Input,
side == INPUT ? kAudioUnitOutputBus : kAudioUnitInputBus,
&enable,
sizeof(enable));
if (r != noErr) {
LOG("%d: Could not set IO for %s device\n", r, side == INPUT ? "input" : "output");
return false;
}
if (id == 0) {
id = GetDefaultDeviceId(side);
}
r = AudioUnitSetProperty(unit,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
side == INPUT ? kAudioUnitInputBus : kAudioUnitOutputBus,
&id, sizeof(id));
if (r != noErr) {
LOG("%d: Could not configure for %s device by kAudioOutputUnitProperty_CurrentDevice\n", r, side == INPUT ? "input" : "output");
return false;
}
// Check if we have IO
size = sizeof(hasIO);
r = AudioUnitGetProperty(unit,
kAudioOutputUnitProperty_HasIO,
side == INPUT ? kAudioUnitScope_Input : kAudioUnitScope_Output,
side == INPUT ? kAudioUnitInputBus : kAudioUnitOutputBus,
&hasIO,
&size);
if (!hasIO) {
LOG("%d: No IO for %s device\n", r, side == INPUT ? "input" : "output");
return false;
}
assert(GetCurrentDeviceId(unit, side) == id);
return true;
}
Layout
GetCurrentChannelLayout(AudioUnit& unit, Side side)
{
UInt32 size = 0;
OSStatus r = noErr;
Boolean writable;
r = AudioUnitGetPropertyInfo(unit,
kAudioUnitProperty_AudioChannelLayout,
side == INPUT ? kAudioUnitScope_Input : kAudioUnitScope_Output,
side == INPUT ? kAudioUnitInputBus : kAudioUnitOutputBus,
&size,
&writable);
if (r != noErr) {
LOG("%d: Could not get size of kAudioUnitProperty_AudioChannelLayout.\n", r);
return SMPTE_UNDEFINED;
}
assert(size > 0);
auto layout = AllocAudioChannelLayout(size);
r = AudioUnitGetProperty(unit,
kAudioUnitProperty_AudioChannelLayout,
side == INPUT ? kAudioUnitScope_Input : kAudioUnitScope_Output,
side == INPUT ? kAudioUnitInputBus : kAudioUnitOutputBus,
layout.get(),
&size);
if (r != noErr) {
LOG("%d: Could not get kAudioUnitProperty_AudioChannelLayout.\n", r);
return SMPTE_UNDEFINED;
}
return ConvertToSMPTE(layout.get());
}
bool
SetChannelLayout(AudioUnit& unit, Side side, Layout layout)
{
assert(layout != SMPTE_UNDEFINED);
LOG("Try setting channel layout to %s for %s device\n", LAYOUT_INFO[layout].name, side == INPUT ? "input" : "output");
size_t size = sizeof(AudioChannelLayout);
OSStatus r;
auto coreLayout = AllocAudioChannelLayout(size);
switch (layout) {
case SMPTE_MONO:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
break;
case SMPTE_STEREO:
case SMPTE_DUAL_MONO:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
break;
case SMPTE_3F:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_3_0;
break;
case SMPTE_2F1:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_2_1;
break;
case SMPTE_3F1:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_3_1;
break;
case SMPTE_2F2:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_2_2;
break;
case SMPTE_3F2:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_ITU_3_2;
break;
case SMPTE_3F2_LFE:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_AudioUnit_5_1;
break;
default:
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_Unknown;
break;
}
// For those layouts that can't be matched to coreaudio's predefined layout,
// we use customized layout.
if (coreLayout.get()->mChannelLayoutTag == kAudioChannelLayoutTag_Unknown) {
size = fieldOffset(AudioChannelLayout, mChannelDescriptions[LAYOUT_INFO[layout].channels]);
coreLayout = AllocAudioChannelLayout(size);
coreLayout.get()->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
coreLayout.get()->mNumberChannelDescriptions = LAYOUT_INFO[layout].channels;
for (UInt32 i = 0 ; i < coreLayout.get()->mNumberChannelDescriptions ; ++i) {
coreLayout.get()->mChannelDescriptions[i].mChannelLabel =
ChannelToLabel(CHANNEL_INDEX_TO_ORDER[layout][i]);
coreLayout.get()->mChannelDescriptions[i].mChannelFlags = kAudioChannelFlags_AllOff;
}
}
r = AudioUnitSetProperty(unit,
kAudioUnitProperty_AudioChannelLayout,
side == INPUT ? kAudioUnitScope_Output : kAudioUnitScope_Input,
side == INPUT ? kAudioUnitInputBus : kAudioUnitOutputBus,
coreLayout.get(),
size);
if (r != noErr) {
LOG("%d: Could not set channel layout to %s for %s device\n", r, LAYOUT_INFO[layout].name, side == INPUT ? "input" : "output");
return false;
}
return true;
}
int main()
{
// Output
AudioDevice outDev;
if (GetAudioUnit(outDev.unit, OUTPUT, 0)) {
outDev.layout = GetCurrentChannelLayout(outDev.unit, OUTPUT);
printf("Output >> layout: %s, preferred: %s\n", LAYOUT_INFO[outDev.layout].name, LAYOUT_INFO[GetPreferredChannelLayout(OUTPUT)].name);
if (SetChannelLayout(outDev.unit, OUTPUT, SMPTE_STEREO)) {
outDev.layout = GetCurrentChannelLayout(outDev.unit, OUTPUT);
printf("Output layout is set to: %s\n", LAYOUT_INFO[outDev.layout].name);
}
}
// Input
AudioDevice inDev;
if (GetAudioUnit(inDev.unit, INPUT, 0)) {
inDev.layout = GetCurrentChannelLayout(inDev.unit, INPUT);
printf("Input >> layout: %s, preferred: %s\n", LAYOUT_INFO[inDev.layout].name, LAYOUT_INFO[GetPreferredChannelLayout(INPUT)].name);
if (SetChannelLayout(inDev.unit, INPUT, SMPTE_MONO)) {
inDev.layout = GetCurrentChannelLayout(inDev.unit, INPUT);
printf("Input layout is set to: %s\n", LAYOUT_INFO[inDev.layout].name);
}
}
return 0;
}
CXX=g++
CFLAGS=-lc++ -Wall -std=c++14
LIBRARIES=-framework CoreAudio -framework AudioUnit
all:
$(CC) $(CFLAGS) $(LIBRARIES) ChannelLayout.cpp -o ChannelLayout
clean:
rm ChannelLayout
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment