,---. . ,-_/
| -' ,-. ,-. |- ,-. . ,-. ' | ,-. ,-. . ,
| . ,-| | | | ,-| | | | | ,-| | |/
`---' `-^ |-' `' `-^ ' ' ' | `-^ `-' |\
| / | ' `
' `--'
captain jack audio device
github.com/qix-/captainjack
copyright (c) 2016 josh junon
released under the MIT license
I've halted production on Captain Jack until JACK has improved. See #3.
Captain Jack is a JACK-enabled audio device for OS/X.
Through the use of the new AudioServerPlugin API (since 10.5), Captain Jack provides a means of routing system audio into the JACK subsystem through either a system mix port or one or more per-application (namely, per-process) audio ports.
You must have an implementation of JACK installed on the system. You can (and should) do this by downloading and building the latest source from either JACK1 or JACK2, though it's worth mentioning JACK2 does not currently build on OS/X.
To build Captain Jack:
$ make
To install Captain Jack:
$ sudo make install
To start Captain Jack:
$ sudo make start
Use Console.app
(in /Applications/Utilities). Select system.log
on the side
and filter for CaptainJack
.
All daemon log messages have the CaptainJack
tag, and all device messages
have the CaptainJack-Device
tag.
Captain Jack is made up of two pieces: the device and the daemon.
The Captain Jack Device is a CoreAudio HAL Plugin .driver
bundle that uses
the latest APIs in lieu of the deprecated AudioHardwarePlugin APIs.
The device is a user-space plugin that requires no Kext signing nor does it require disabling of SIP.
Since coreaudiod
, the system service that manages audio and thus loads
Captain Jack's device plugin, resides within the
System bootstrap
space, and since coreaudiod
runs everything inside a sandbox, getting
audio out of coreaudiod
and into JACK requires some clever IPC.
XPC is Apple's version of IPC and is one of the two methods of IPC really
available to Mach services. Its major (and deal-breaking) drawback is that
it requires some sort of main dispatch loop, which isn't possible in an
AudioServerPlugin bundle since everything inside of the device is just a
callback and anything that blocks would effectively block not only coreaudiod
but other launch daemons as well (I notice my RGB key board freeze up if
Captain Jack blocks!).
Therefore, the network was the only other means of IPC (I heard that shared memory regions were allowed, but I never could figure out how to get them to work).
When a connection has been established to the daemon (a user-space launchd
daemon; see below), all callbacks forward their data to an Xmit RPC call (see
src/xmit.c
) that in turn passes the data along the loopback socket and into
the less-restrictive daemon process.
The reason why the daemon is even necessary and why the device cannot simply connect to JACK directly is that the sandbox prevents any IPC other than network IPC. Since we can't really guarantee that JACK is going to support network communication this becomes unreliable. As well, things like RO filesystem calls, the inability to trigger window apps or even look up PID process names were very restrictive, and the device quickly turned into a trampoline for audio events.
The Captain Jack Daemon is a user-space launchd
LaunchDaemon that relays
Xmit-RPC'd audio data from the device to JACK. It also manages a light state
for whatever might be necessary to track (e.g. client name => PID map, etc).
The device and daemon communicate over a very opaque and light network layer
dubbed Xmit (see src/xmit.c
) that uses read buffer polling to achieve
asynchronicity. Since it uses the TCP protocol, there is little chance of
audio frames to be mixed up during transmission.
Using the network also gives us nearly free IPC with almost zero added latency (assuming the loopback interface is used, which it is in this case).
The only externalized Xmit calls are those that set up the callback functions.
All transportation specifics are statically defined and managed inside of
xmit.c
.
Captain Jack is licensed under the MIT License. All JACK works are licensed under their respective licenses.