- 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 allif(r != noErr)
- Write some sample code for
kAudioOutputUnitProperty_ChannelMap
Last active
June 5, 2020 09:52
-
-
Save ChunMinChang/ea74c8228745449873716e1d98ba956e to your computer and use it in GitHub Desktop.
CoreAudio Playground #coreaudio #multichannel_audio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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