Skip to content

Commit

Permalink
wip feat(cli): add CLI tool to set output device
Browse files Browse the repository at this point in the history
Summary:
Add a simple CLI tool to configure proxy audio devices.

Right now this is broken.  I'm committing it so I can ask for help.

Reproduction steps:
1. In the source code for `main.cpp:main`, change the invocation of
`select_audio_device` to be a non-proxy audio device on your system
2. In Xcode, run the `proxy-audio-device-cli` target
3. It doesn't work!! 😭

This is what's confusing me:

4. Put a breakpoint in `AudioDevice.cpp:setObjectPropertyName:329`
5. Try setting the same device through the CLI program again, and note the
parameters we call `AudioObjectSetPropertyData` with
6. Try setting the same device through the GUI, and note the paramaters we call
`AudioObjectSetPropertyData` with
7. It's the same thing!

I'm not sure why the GUI works and the CLI doesn't.  I'm hoping someone can
help me figure it out.
  • Loading branch information
vegerot committed May 19, 2023
1 parent 67b47d8 commit 0ac8271
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 16 deletions.
4 changes: 3 additions & 1 deletion ProxyAudioDeviceSettings/WindowDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,10 @@ - (IBAction)outputDeviceSelected:(id)sender {
}

AudioDeviceID proxyAudioBox = AudioDevice::audioDeviceIDForBoxUID(CFSTR(kBox_UID));
NSString *formattedThing = [NSString stringWithFormat:@"outputDevice=%@", uid];
NSLog(@"%@\n", formattedThing);
AudioDevice::setObjectName(proxyAudioBox,
(__bridge_retained CFStringRef)[NSString stringWithFormat:@"outputDevice=%@", uid]);
(__bridge_retained CFStringRef)formattedThing);
}

- (NSString *)currentOutputDeviceBufferFrameSize {
Expand Down
132 changes: 132 additions & 0 deletions proxy-audio-device-cli/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// main.cpp
// proxy-audio-device-cli
//
// Created by Max Coplan on 5/19/23.
//

#include <CoreAudio/CoreAudio.h>
#include <iostream>
#include <map>
#include <vector>

#include "AudioDevice.h"
#include "ProxyAudioDevice.h"

static void list_audio_devices() {
std::vector<AudioObjectID> currentDeviceList =
AudioDevice::devicesWithOutputCapabilitiesThatAreNotProxyAudioDevice();
for (AudioObjectID &deviceId : currentDeviceList) {
CFStringRef name_ref = AudioDevice::copyObjectName(deviceId);
char const *name = CFStringGetCStringPtr(
name_ref, CFStringBuiltInEncodings::kCFStringEncodingUTF8);
if (name == nullptr) {
CFIndex size = (sizeof(char) * CFStringGetLength(name_ref)) + 1;
char *buf = (char *)malloc(size);
bool success = CFStringGetCString(
name_ref, buf, size, CFStringBuiltInEncodings::kCFStringEncodingUTF8);
assert(success);
name = buf;
}
std::printf("%s\n", name);
}
}
/**
TODO: don't get all the audio devices, just search for the name you're looking
for
*/
static std::map</** name*/ std::string, /**uid*/ std::string>
get_audio_devices() {
std::map<std::string, std::string> audio_devices;
std::vector<AudioObjectID> currentDeviceList =
AudioDevice::devicesWithOutputCapabilitiesThatAreNotProxyAudioDevice();
for (AudioObjectID &deviceId : currentDeviceList) {
std::string name;
{
CFStringRef name_ref = AudioDevice::copyObjectName(deviceId);
char const *name_cstr = CFStringGetCStringPtr(
name_ref, CFStringBuiltInEncodings::kCFStringEncodingUTF8);
if (name_cstr == nullptr) {
CFIndex size = (sizeof(char) * CFStringGetLength(name_ref)) + 1;
char *buf = (char *)malloc(size);
bool success =
CFStringGetCString(name_ref, buf, size,
CFStringBuiltInEncodings::kCFStringEncodingUTF8);
assert(success);
name_cstr = buf;
}
name = name_cstr;
}
std::string uid;
{
CFStringRef device_uid_ref = AudioDevice::copyDeviceUID(deviceId);
char const *device_uid = CFStringGetCStringPtr(
device_uid_ref, CFStringBuiltInEncodings::kCFStringEncodingUTF8);
if (!device_uid) {
CFIndex size = (sizeof(char) * CFStringGetLength(device_uid_ref)) + 1;
char *buf = (char *)malloc(size);
bool success =
CFStringGetCString(device_uid_ref, buf, size,
CFStringBuiltInEncodings::kCFStringEncodingUTF8);
assert(success);
device_uid = buf;
}
uid = device_uid;
}
audio_devices.insert({name, uid});
}
return audio_devices;
}

static std::string find_audio_device_uid_from_name(std::string device_name) {

std::map<std::string, std::string> audio_devices = get_audio_devices();
std::map<std::string, std::string>::iterator device_found =
audio_devices.find(device_name);
assert(device_found != audio_devices.end());
std::string device_uid = device_found->second;
return device_uid;
}

static AudioDeviceID audioDeviceIDForBoxUID(std::string device_uid) {
return AudioDevice::audioDeviceIDForBoxUID(CFStringCreateWithCString(
nullptr, device_uid.c_str(),
CFStringBuiltInEncodings::kCFStringEncodingUTF8));
}

static void set_audio_device_as_active(std::string device_uid) {
AudioDeviceID proxy_audio_box = audioDeviceIDForBoxUID(kBox_UID);
std::string formatted_device_uid = ({
static constexpr char output_literal[] = "outputDevice=";
std::size_t output_literal_len = sizeof(output_literal) - 1;
std::size_t sizeof_the_thing =
sizeof(char) * (output_literal_len + device_uid.size()) + 1;
char *buf = (char *)malloc(sizeof_the_thing);
for (std::size_t i = 0; i < output_literal_len; ++i) {
buf[i] = output_literal[i];
}
for (std::size_t i = 0; i < device_uid.size(); ++i) {
buf[i + output_literal_len] = device_uid[i];
}
buf;
});
printf("%s\n", formatted_device_uid.c_str());

AudioDevice::setObjectName(
proxy_audio_box, CFStringCreateWithCString(
nullptr, formatted_device_uid.c_str(),
CFStringBuiltInEncodings::kCFStringEncodingUTF8));
}

static void select_audio_device(std::string device_name) {
std::string device_uid = find_audio_device_uid_from_name(device_name);
set_audio_device_as_active(device_uid);
return;
}

int main(int argc, const char *argv[]) {
list_audio_devices();
select_audio_device("MacBook Pro Speakers");

return 0;
}
8 changes: 8 additions & 0 deletions proxy-audio-device-cli/proxy-audio-device-cli.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
</plist>
Loading

0 comments on commit 0ac8271

Please sign in to comment.