Hard Core Audio

* allow to change buffersizes
* subscribe to buffersize & samplerate changes
* add support for half-duplex devices.
* aggregate Devices (not yet used) code from JACK2
* unify deprecated API wrappers
* properly keep track of MIDI ports
* disable MidiI/O during freewheeling
* various small fixes & cleanup
This commit is contained in:
Robin Gareus 2015-03-07 04:15:02 +01:00
parent e99599c7db
commit ce3adfd3d4
8 changed files with 1141 additions and 436 deletions

View File

@ -61,6 +61,18 @@ static void xrun_callback_ptr (void *arg)
d->xrun_callback();
}
static void buffer_size_callback_ptr (void *arg)
{
CoreAudioBackend *d = static_cast<CoreAudioBackend*> (arg);
d->buffer_size_callback();
}
static void sample_rate_callback_ptr (void *arg)
{
CoreAudioBackend *d = static_cast<CoreAudioBackend*> (arg);
d->sample_rate_callback();
}
static void midi_port_change (void *arg)
{
CoreAudioBackend *d = static_cast<CoreAudioBackend *>(arg);
@ -99,7 +111,6 @@ CoreAudioBackend::CoreAudioBackend (AudioEngine& e, AudioBackendInfo& info)
_pcmio->set_hw_changed_callback (hw_changed_callback_ptr, this);
_pcmio->discover();
_midiio->discover();
}
CoreAudioBackend::~CoreAudioBackend ()
@ -175,7 +186,7 @@ CoreAudioBackend::can_change_sample_rate_when_running () const
bool
CoreAudioBackend::can_change_buffer_size_when_running () const
{
return false;
return true;
}
int
@ -204,6 +215,7 @@ CoreAudioBackend::set_buffer_size (uint32_t bs)
return -1;
}
_samples_per_period = bs;
_pcmio->set_samples_per_period(bs);
engine.buffer_size_change (bs);
return 0;
}
@ -428,7 +440,8 @@ CoreAudioBackend::_start (bool for_latency_measurement)
_ports.clear();
}
uint32_t device_id = name_to_id(_audio_device);
uint32_t device1 = name_to_id(_audio_device); // usually input, but in an aggregate, 1st defines the clock
uint32_t device2 = name_to_id(_audio_device); // usually output
assert(_active_ca == false);
assert(_active_fw == false);
@ -437,7 +450,10 @@ CoreAudioBackend::_start (bool for_latency_measurement)
_reinit_thread_callback = true;
_pcmio->set_error_callback (error_callback_ptr, this);
_pcmio->pcm_start (device_id, device_id, _samplerate, _samples_per_period, process_callback_ptr, this);
_pcmio->set_buffer_size_callback (buffer_size_callback_ptr, this);
_pcmio->set_sample_rate_callback (sample_rate_callback_ptr, this);
_pcmio->pcm_start (device1, device2, _samplerate, _samples_per_period, process_callback_ptr, this);
switch (_pcmio->state ()) {
case 0: /* OK */ break;
@ -454,7 +470,7 @@ CoreAudioBackend::_start (bool for_latency_measurement)
} else {
_n_outputs = std::min (_n_outputs, _pcmio->n_playback_channels ());
}
PBD::warning << _("CoreAudioBackend: adjusted output channel count to match device.") << endmsg;
PBD::info << _("CoreAudioBackend: adjusted output channel count to match device.") << endmsg;
}
if (_n_inputs != _pcmio->n_capture_channels ()) {
@ -463,18 +479,16 @@ CoreAudioBackend::_start (bool for_latency_measurement)
} else {
_n_inputs = std::min (_n_inputs, _pcmio->n_capture_channels ());
}
PBD::warning << _("CoreAudioBackend: adjusted input channel count to match device.") << endmsg;
PBD::info << _("CoreAudioBackend: adjusted input channel count to match device.") << endmsg;
}
#if 0 // TODO
if (_pcmio->sample_per_period() != _samples_per_period) {
_samples_per_period = _pcmio->sample_per_period();
if (_pcmio->samples_per_period() != _samples_per_period) {
_samples_per_period = _pcmio->samples_per_period();
PBD::warning << _("CoreAudioBackend: samples per period does not match.") << endmsg;
}
#endif
if (_pcmio->current_sample_rate(name_to_id(_audio_device)) != _samplerate) {
_samplerate = _pcmio->current_sample_rate(name_to_id(_audio_device));
if (_pcmio->sample_rate() != _samplerate) {
_samplerate = _pcmio->sample_rate();
engine.sample_rate_change (_samplerate);
PBD::warning << _("CoreAudioBackend: sample rate does not match.") << endmsg;
}
@ -486,8 +500,9 @@ CoreAudioBackend::_start (bool for_latency_measurement)
_port_change_flag = false;
if (_midi_driver_option == _("CoreMidi")) {
_midiio->set_enabled(true);
_midiio->set_port_changed_callback(midi_port_change, this);
_midiio->discover();
_midiio->start();
}
if (register_system_audio_ports()) {
@ -555,6 +570,7 @@ CoreAudioBackend::stop ()
_run = false;
_pcmio->pcm_stop();
_midiio->set_port_changed_callback(NULL, NULL);
_midiio->stop();
if (pthread_join (_freeewheel_thread, &status)) {
PBD::error << _("CoreAudioBackend: failed to terminate.") << endmsg;
@ -866,8 +882,8 @@ CoreAudioBackend::register_system_audio_ports()
{
LatencyRange lr;
const int a_ins = _n_inputs > 0 ? _n_inputs : 2;
const int a_out = _n_outputs > 0 ? _n_outputs : 2;
const int a_ins = _n_inputs;
const int a_out = _n_outputs;
const uint32_t coreaudio_reported_input_latency = _pcmio->get_latency(name_to_id(_audio_device), true);
const uint32_t coreaudio_reported_output_latency = _pcmio->get_latency(name_to_id(_audio_device), false);
@ -901,42 +917,6 @@ CoreAudioBackend::register_system_audio_ports()
return 0;
}
int
CoreAudioBackend::register_system_midi_ports()
{
int midi_ins = _system_midi_out.size();
int midi_outs = _system_midi_in.size();
//TODO query port names
for (uint32_t i = midi_ins; i < _midiio->n_midi_outputs(); ++i) {
char tmp[64];
snprintf(tmp, sizeof(tmp), "system:midi_playback_%d", ++midi_ins);
PortHandle p = add_port(std::string(tmp), DataType::MIDI, static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
if (!p) {
continue;
}
LatencyRange lr;
lr.min = lr.max = _samples_per_period; // TODO add per-port midi-systemic latency
set_latency_range (p, false, lr);
_system_midi_out.push_back(static_cast<CoreBackendPort*>(p));
}
for (uint32_t i = midi_outs; i < _midiio->n_midi_inputs(); ++i) {
char tmp[64];
snprintf(tmp, sizeof(tmp), "system:midi_capture_%d", ++midi_outs);
PortHandle p = add_port(std::string(tmp), DataType::MIDI, static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
if (!p) {
continue;
}
LatencyRange lr;
lr.min = lr.max = _samples_per_period; // TODO add per-port midi-systemic latency
set_latency_range (p, false, lr);
_system_midi_in.push_back(static_cast<CoreBackendPort*>(p));
}
return 0;
}
void
CoreAudioBackend::coremidi_rediscover()
{
@ -945,23 +925,92 @@ CoreAudioBackend::coremidi_rediscover()
pthread_mutex_lock (&_process_callback_mutex);
// TODO maintain device-specific connections, rather
// than re-map.
while (_system_midi_out.size() > _midiio->n_midi_outputs()) {
CoreBackendPort* p = _system_midi_out.back();
_system_midi_out.pop_back();
unregister_port(p);
for (std::vector<CoreBackendPort*>::iterator it = _system_midi_out.begin (); it != _system_midi_out.end ();) {
bool found = false;
for (int i = 0; i < _midiio->n_midi_outputs(); ++i) {
if ((*it)->name() == _midiio->port_name(i, false)) {
found = true;
break;
}
}
if (found) {
++it;
} else {
#ifndef NDEBUG
printf("unregister MIDI Output: %s\n", (*it)->name().c_str());
#endif
_port_change_flag = true;
unregister_port((*it));
it = _system_midi_out.erase(it);
}
}
while (_system_midi_in.size() > _midiio->n_midi_inputs()) {
CoreBackendPort* p = _system_midi_in.back();
_system_midi_in.pop_back();
unregister_port(p);
for (std::vector<CoreBackendPort*>::iterator it = _system_midi_in.begin (); it != _system_midi_in.end ();) {
bool found = false;
for (int i = 0; i < _midiio->n_midi_inputs(); ++i) {
if ((*it)->name() == _midiio->port_name(i, true)) {
found = true;
break;
}
}
if (found) {
++it;
} else {
#ifndef NDEBUG
printf("unregister MIDI Input: %s\n", (*it)->name().c_str());
#endif
_port_change_flag = true;
unregister_port((*it));
it = _system_midi_in.erase(it);
}
}
register_system_midi_ports();
for (int i = 0; i < _midiio->n_midi_inputs(); ++i) {
std::string name = _midiio->port_name(i, true);
if (find_port_in(_system_midi_in, name)) {
continue;
}
#ifndef NDEBUG
printf("register MIDI Input: %s\n", name.c_str());
#endif
PortHandle p = add_port(name, DataType::MIDI, static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
if (!p) {
fprintf(stderr, "failed to register MIDI IN: %s\n", name.c_str());
continue;
}
LatencyRange lr;
lr.min = lr.max = _samples_per_period; // TODO add per-port midi-systemic latency
set_latency_range (p, false, lr);
_system_midi_in.push_back(static_cast<CoreBackendPort*>(p));
_port_change_flag = true;
}
for (int i = 0; i < _midiio->n_midi_outputs(); ++i) {
std::string name = _midiio->port_name(i, false);
if (find_port_in(_system_midi_out, name)) {
continue;
}
#ifndef NDEBUG
printf("register MIDI OUT: %s\n", name.c_str());
#endif
PortHandle p = add_port(name, DataType::MIDI, static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
if (!p) {
fprintf(stderr, "failed to register MIDI OUT: %s\n", name.c_str());
continue;
}
LatencyRange lr;
lr.min = lr.max = _samples_per_period; // TODO add per-port midi-systemic latency
set_latency_range (p, false, lr);
_system_midi_out.push_back(static_cast<CoreBackendPort*>(p));
_port_change_flag = true;
}
assert(_system_midi_out.size() == _midiio->n_midi_outputs());
assert(_system_midi_in.size() == _midiio->n_midi_inputs());
_port_change_flag = true;
pthread_mutex_unlock (&_process_callback_mutex);
}
@ -1339,6 +1388,7 @@ CoreAudioBackend::freewheel_thread ()
// handshake w/ coreaudio
_reinit_thread_callback = true;
_freewheel_ack = false;
_midiio->set_enabled(true);
}
engine.freewheel_callback (_freewheeling);
@ -1356,8 +1406,12 @@ CoreAudioBackend::freewheel_thread ()
first_run = false;
_main_thread = pthread_self();
AudioEngine::thread_init_callback (this);
_midiio->set_enabled(false);
}
post_process();
pthread_mutex_lock (&_process_callback_mutex);
// Freewheelin'
for (std::vector<CoreBackendPort*>::const_iterator it = _system_inputs.begin (); it != _system_inputs.end (); ++it) {
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
@ -1367,17 +1421,18 @@ CoreAudioBackend::freewheel_thread ()
}
if (engine.process_callback (_samples_per_period)) {
pthread_mutex_unlock (&_process_callback_mutex);
break;
}
pthread_mutex_unlock (&_process_callback_mutex);
_dsp_load = 1.0;
Glib::usleep (100); // don't hog cpu
post_process();
}
_active_fw = false;
if (_run && _freewheel) {
if (_run) {
engine.halted_callback("CoreAudio Freehweeling aborted.");
}
return 0;
@ -1412,16 +1467,10 @@ CoreAudioBackend::process_callback ()
manager.graph_order_callback();
}
const uint32_t n_samples = _pcmio->n_samples();
/* port-connection change */
post_process();
#if 0 // here in RT callback ?? XXX
if (_samples_per_period != n_samples) {
printf("CoreAudio Adjust SPP %zu -> %d\n", _samples_per_period, n_samples);
_samples_per_period = n_samples;
engine.buffer_size_change (_samples_per_period);
// TODO update latencies
}
#endif
const uint32_t n_samples = _pcmio->n_samples();
// cycle-length in usec
const int64_t nominal_time = 1e6 * n_samples / _samplerate;
@ -1492,8 +1541,6 @@ CoreAudioBackend::process_callback ()
const int64_t elapsed_time = clock2 - clock1;
_dsp_load = elapsed_time / (float) nominal_time;
/* port-connection change */
post_process();
pthread_mutex_unlock (&_process_callback_mutex);
return 0;
}
@ -1502,7 +1549,11 @@ void
CoreAudioBackend::error_callback ()
{
_pcmio->set_error_callback (NULL, NULL);
_pcmio->set_sample_rate_callback (NULL, NULL);
_pcmio->set_xrun_callback (NULL, NULL);
_midiio->set_port_changed_callback(NULL, NULL);
engine.halted_callback("CoreAudio Process aborted.");
_active_ca = false;
}
void
@ -1511,6 +1562,28 @@ CoreAudioBackend::xrun_callback ()
engine.Xrun ();
}
void
CoreAudioBackend::buffer_size_callback ()
{
uint32_t bs = _pcmio->samples_per_period();
if (bs == _samples_per_period) {
return;
}
_samples_per_period = bs;
engine.buffer_size_change (_samples_per_period);
}
void
CoreAudioBackend::sample_rate_callback ()
{
_pcmio->set_error_callback (NULL, NULL);
_pcmio->set_sample_rate_callback (NULL, NULL);
_pcmio->set_xrun_callback (NULL, NULL);
_midiio->set_port_changed_callback(NULL, NULL);
engine.halted_callback("Sample Rate Changed.");
stop();
}
void
CoreAudioBackend::hw_changed_callback ()
{

View File

@ -215,6 +215,8 @@ class CoreAudioBackend : public AudioBackend {
int process_callback();
void error_callback();
void xrun_callback();
void buffer_size_callback();
void sample_rate_callback();
void hw_changed_callback();
protected:
@ -376,7 +378,6 @@ class CoreAudioBackend : public AudioBackend {
/* port engine */
PortHandle add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags);
int register_system_audio_ports ();
int register_system_midi_ports ();
void unregister_ports (bool system_only = false);
std::vector<CoreBackendPort *> _ports;
@ -423,6 +424,15 @@ class CoreAudioBackend : public AudioBackend {
return NULL;
}
CoreBackendPort * find_port_in (std::vector<CoreBackendPort *> plist, const std::string& port_name) const {
for (std::vector<CoreBackendPort*>::const_iterator it = plist.begin (); it != plist.end (); ++it) {
if ((*it)->name () == port_name) {
return *it;
}
}
return NULL;
}
}; // class CoreAudioBackend
} // namespace

File diff suppressed because it is too large Load Diff

View File

@ -42,15 +42,20 @@ public:
uint32_t n_capture_channels (void) const { return _capture_channels; }
void discover();
void device_list(std::map<size_t, std::string> &devices) const { devices = _devices;}
void device_list (std::map<size_t, std::string> &devices) const { devices = _devices;}
int available_sample_rates (uint32_t device_id, std::vector<float>& sampleRates);
int available_buffer_sizes (uint32_t device_id, std::vector<uint32_t>& sampleRates);
uint32_t available_channels (uint32_t device_id, bool input);
float current_sample_rate(uint32 device_id, bool input = false);
uint32_t get_latency(uint32 device_id, bool input);
float current_sample_rate (uint32_t device_id, bool input = false);
uint32_t get_latency (uint32_t device_id, bool input);
std::string cached_port_name(uint32_t portnum, bool input) const;
std::string cached_port_name (uint32_t portnum, bool input) const;
float sample_rate ();
uint32_t samples_per_period () const { return _samples_per_period; };
int set_samples_per_period (uint32_t);
void launch_control_app (uint32_t device_id);
@ -79,6 +84,7 @@ public:
_hw_changed_callback = callback;
_hw_changed_arg = arg;
}
void set_xrun_callback (
void ( callback (void*)),
void * arg
@ -86,6 +92,20 @@ public:
_xrun_callback = callback;
_xrun_arg = arg;
}
void set_buffer_size_callback (
void ( callback (void*)),
void * arg
) {
_buffer_size_callback = callback;
_buffer_size_arg = arg;
}
void set_sample_rate_callback (
void ( callback (void*)),
void * arg
) {
_sample_rate_callback = callback;
_sample_rate_arg = arg;
}
// must be called from process_callback;
int get_capture_channel (uint32_t chn, float *input, uint32_t n_samples);
@ -101,24 +121,39 @@ public:
AudioBufferList* ioData);
void xrun_callback ();
void buffer_size_callback ();
void sample_rate_callback ();
void hw_changed_callback ();
private:
int set_device_sample_rate (uint32 device_id, float rate, bool input);
void get_stream_latencies(uint32 device_id, bool input, std::vector<uint32>& latencies);
void cache_port_names(uint32 device_id, bool input);
float current_sample_rate_id (AudioDeviceID id, bool input);
uint32_t current_buffer_size_id (AudioDeviceID id);
int set_device_sample_rate_id (AudioDeviceID id, float rate, bool input);
int set_device_buffer_size_id (AudioDeviceID id, uint32_t samples_per_period);
int set_device_sample_rate (uint32_t device_id, float rate, bool input);
void get_stream_latencies (uint32_t device_id, bool input, std::vector<uint32_t>& latencies);
void cache_port_names (AudioDeviceID id, bool input);
void destroy_aggregate_device();
int create_aggregate_device(
AudioDeviceID input_device_id,
AudioDeviceID output_device_id,
uint32_t sample_rate,
AudioDeviceID *created_device);
AudioUnit _auhal;
AudioDeviceID* _device_ids;
AudioBufferList* _input_audio_buffer_list;
AudioBufferList* _output_audio_buffer_list;
AudioDeviceID _active_input;
AudioDeviceID _active_output;
AudioDeviceID _active_device_id;
AudioDeviceID _aggregate_device_id;
AudioDeviceID _aggregate_plugin_id;
int _state;
uint32_t _max_samples_per_period;
uint32_t _samples_per_period;
uint32_t _cur_samples_per_period;
uint32_t _capture_channels;
uint32_t _playback_channels;
@ -137,6 +172,12 @@ private:
void (* _xrun_callback) (void*);
void * _xrun_arg;
void (* _buffer_size_callback) (void*);
void * _buffer_size_arg;
void (* _sample_rate_callback) (void*);
void * _sample_rate_arg;
// TODO proper device info struct
std::map<size_t, std::string> _devices;

View File

@ -0,0 +1,358 @@
/*
* Copyright (C) 2015 Robin Gareus <robin@gareus.org>
* Copyright (C) 2004-2008 Grame
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <vector>
void
CoreAudioPCM::destroy_aggregate_device ()
{
if (_aggregate_plugin_id == 0) {
return;
}
OSStatus err;
AudioObjectPropertyAddress property_address;
property_address.mSelector = kAudioPlugInDestroyAggregateDevice;
property_address.mScope = kAudioObjectPropertyScopeGlobal;
property_address.mElement = kAudioObjectPropertyElementMaster;
UInt32 outDataSize;
err = AudioObjectGetPropertyDataSize(_aggregate_plugin_id, &property_address, 0, NULL, &outDataSize);
if (err != noErr) {
fprintf(stderr, "DestroyAggregateDevice : AudioObjectGetPropertyDataSize error\n");
return;
}
err = AudioObjectGetPropertyData(_aggregate_plugin_id, &property_address, 0, NULL, &outDataSize, &_aggregate_device_id);
if (err != noErr) {
fprintf(stderr, "DestroyAggregateDevice : AudioObjectGetPropertyData error\n");
return;
}
#ifndef NDEBUG
printf("DestroyAggregateDevice : OK (plugin: %u device:%u)\n", _aggregate_plugin_id, _aggregate_device_id);
#endif
}
int
CoreAudioPCM::create_aggregate_device (
AudioDeviceID input_device_id,
AudioDeviceID output_device_id,
uint32_t sample_rate,
AudioDeviceID* created_device)
{
OSStatus err;
AudioObjectID sub_device[32];
UInt32 size = sizeof(sub_device);
/* look up sub-devices */
err = GetPropertyWrapper (input_device_id, 0, 0, kAudioAggregateDevicePropertyActiveSubDeviceList, &size, sub_device);
std::vector<AudioDeviceID> input_device_ids;
if (err != noErr) {
input_device_ids.push_back(input_device_id);
} else {
uint32_t num_devices = size / sizeof(AudioObjectID);
for (uint32_t i = 0; i < num_devices; ++i) {
input_device_ids.push_back(sub_device[i]);
}
}
size = sizeof(sub_device);
err = GetPropertyWrapper (output_device_id, 0, 0, kAudioAggregateDevicePropertyActiveSubDeviceList, &size, sub_device);
std::vector<AudioDeviceID> output_device_ids;
if (err != noErr) {
output_device_ids.push_back(output_device_id);
} else {
uint32_t num_devices = size / sizeof(AudioObjectID);
for (uint32_t i = 0; i < num_devices; ++i) {
output_device_ids.push_back(sub_device[i]);
}
}
//---------------------------------------------------------------------------
// Setup SR of both devices otherwise creating AD may fail...
//---------------------------------------------------------------------------
UInt32 keptclockdomain = 0;
UInt32 clockdomain = 0;
size = sizeof(UInt32);
bool need_clock_drift_compensation = false;
for (size_t i = 0; i < input_device_ids.size(); ++i) {
set_device_sample_rate_id(input_device_ids[i], sample_rate, true);
// Check clock domain
err = GetPropertyWrapper (input_device_ids[i], 0, 0, kAudioDevicePropertyClockDomain, &size, &clockdomain);
if (err == noErr) {
keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain;
if (clockdomain != 0 && clockdomain != keptclockdomain) {
#ifndef NDEBUG
printf("AggregateDevice: devices do not share the same clock.\n");
#endif
need_clock_drift_compensation = true;
}
}
}
for (UInt32 i = 0; i < output_device_ids.size(); i++) {
set_device_sample_rate_id(output_device_ids[i], sample_rate, true);
// Check clock domain
err = GetPropertyWrapper (output_device_ids[i], 0, 0, kAudioDevicePropertyClockDomain, &size, &clockdomain);
if (err == noErr) {
keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain;
if (clockdomain != 0 && clockdomain != keptclockdomain) {
#ifndef NDEBUG
printf("AggregateDevice: devices do not share the same clock.\n");
#endif
need_clock_drift_compensation = true;
}
}
}
// If no valid clock domain was found, then assume we have to compensate...
if (keptclockdomain == 0) {
need_clock_drift_compensation = true;
}
//---------------------------------------------------------------------------
// Start to create a new aggregate by getting the base audio hardware plugin
//---------------------------------------------------------------------------
#ifndef NDEBUG
char device_name[256];
for (size_t i = 0; i < input_device_ids.size(); ++i) {
GetDeviceNameFromID(input_device_ids[i], device_name);
printf("Separated input = '%s'\n", device_name);
}
for (size_t i = 0; i < output_device_ids.size(); ++i) {
GetDeviceNameFromID(output_device_ids[i], device_name);
printf("Separated output = '%s'\n", device_name);
}
#endif
err = GetHardwarePropertyInfoWrapper (kAudioHardwarePropertyPlugInForBundleID, &size);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: AudioHardwareGetPropertyInfo kAudioHardwarePropertyPlugInForBundleID error\n");
return -1;
}
AudioValueTranslation pluginAVT;
CFStringRef inBundleRef = CFSTR("com.apple.audio.CoreAudio");
pluginAVT.mInputData = &inBundleRef;
pluginAVT.mInputDataSize = sizeof(inBundleRef);
pluginAVT.mOutputData = &_aggregate_plugin_id;
pluginAVT.mOutputDataSize = sizeof(AudioDeviceID);
err = GetHardwarePropertyWrapper (kAudioHardwarePropertyPlugInForBundleID, &size, &pluginAVT);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: AudioHardwareGetProperty kAudioHardwarePropertyPlugInForBundleID error\n");
return -1;
}
//-------------------------------------------------
// Create a CFDictionary for our aggregate device
//-------------------------------------------------
CFMutableDictionaryRef aggDeviceDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef AggregateDeviceNameRef = CFSTR("ArdourDuplex");
CFStringRef AggregateDeviceUIDRef = CFSTR("com.ardour.CoreAudio");
CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceNameKey), AggregateDeviceNameRef);
CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceUIDKey), AggregateDeviceUIDRef);
// hide from list
int value = 1;
CFNumberRef AggregateDeviceNumberRef = CFNumberCreate(NULL, kCFNumberIntType, &value);
CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceIsPrivateKey), AggregateDeviceNumberRef);
//-------------------------------------------------
// Create a CFMutableArray for our sub-device list
//-------------------------------------------------
// we need to append the UID for each device to a CFMutableArray, so create one here
CFMutableArrayRef subDevicesArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
std::vector<CFStringRef> captureDeviceUID;
for (UInt32 i = 0; i < input_device_ids.size(); i++) {
CFStringRef ref = GetDeviceName(input_device_ids[i]);
if (ref == NULL) {
return -1;
}
captureDeviceUID.push_back(ref);
CFArrayAppendValue(subDevicesArray, ref);
}
std::vector<CFStringRef> playbackDeviceUID;
for (UInt32 i = 0; i < output_device_ids.size(); i++) {
CFStringRef ref = GetDeviceName(output_device_ids[i]);
if (ref == NULL) {
return -1;
}
playbackDeviceUID.push_back(ref);
CFArrayAppendValue(subDevicesArray, ref);
}
//-----------------------------------------------------------------------
// Feed the dictionary to the plugin, to create a blank aggregate device
//-----------------------------------------------------------------------
AudioObjectPropertyAddress pluginAOPA;
pluginAOPA.mSelector = kAudioPlugInCreateAggregateDevice;
pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
UInt32 outDataSize;
err = AudioObjectGetPropertyDataSize(_aggregate_plugin_id, &pluginAOPA, 0, NULL, &outDataSize);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: AudioObjectGetPropertyDataSize error\n");
goto error;
}
err = AudioObjectGetPropertyData(_aggregate_plugin_id, &pluginAOPA, sizeof(aggDeviceDict), &aggDeviceDict, &outDataSize, created_device);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: AudioObjectGetPropertyData error\n");
goto error;
}
// pause for a bit to make sure that everything completed correctly
// this is to work around a bug in the HAL where a new aggregate device seems to disappear briefly after it is created
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
//-------------------------
// Set the sub-device list
//-------------------------
pluginAOPA.mSelector = kAudioAggregateDevicePropertyFullSubDeviceList;
pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
outDataSize = sizeof(CFMutableArrayRef);
err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &subDevicesArray);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for sub-device list error\n");
goto error;
}
// pause again to give the changes time to take effect
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
//-----------------------
// Set the master device
//-----------------------
// set the master device manually (this is the device which will act as the master clock for the aggregate device)
// pass in the UID of the device you want to use
pluginAOPA.mSelector = kAudioAggregateDevicePropertyMasterSubDevice;
pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
outDataSize = sizeof(CFStringRef);
err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &captureDeviceUID[0]);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for playback-master device error\n");
// try playback
err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &playbackDeviceUID[0]);
}
if (err != noErr) {
fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for capture-master device error\n");
goto error;
}
// pause again to give the changes time to take effect
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
// Prepare sub-devices for clock drift compensation
// Workaround for bug in the HAL : until 10.6.2
if (need_clock_drift_compensation) {
AudioObjectPropertyAddress theAddressOwned = { kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
AudioObjectPropertyAddress theAddressDrift = { kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
UInt32 theQualifierDataSize = sizeof(AudioObjectID);
AudioClassID inClass = kAudioSubDeviceClassID;
void* theQualifierData = &inClass;
UInt32 subDevicesNum = 0;
#ifndef NDEBUG
printf("Clock drift compensation activated...\n");
#endif
// Get the property data size
err = AudioObjectGetPropertyDataSize(*created_device, &theAddressOwned, theQualifierDataSize, theQualifierData, &size);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: kAudioObjectPropertyOwnedObjects error\n");
}
// Calculate the number of object IDs
subDevicesNum = size / sizeof(AudioObjectID);
#ifndef NDEBUG
printf("AggregateDevice: clock drift compensation, number of sub-devices = %d\n", subDevicesNum);
#endif
AudioObjectID subDevices[subDevicesNum];
size = sizeof(subDevices);
err = AudioObjectGetPropertyData(*created_device, &theAddressOwned, theQualifierDataSize, theQualifierData, &size, subDevices);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: kAudioObjectPropertyOwnedObjects error\n");
}
// Set kAudioSubDevicePropertyDriftCompensation property...
for (UInt32 index = 0; index < subDevicesNum; ++index) {
UInt32 theDriftCompensationValue = 1;
err = AudioObjectSetPropertyData(subDevices[index], &theAddressDrift, 0, NULL, sizeof(UInt32), &theDriftCompensationValue);
if (err != noErr) {
fprintf(stderr, "AggregateDevice: kAudioSubDevicePropertyDriftCompensation error\n");
}
}
}
// pause again to give the changes time to take effect
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
//----------
// Clean up
//----------
// release the private AD key
CFRelease(AggregateDeviceNumberRef);
// release the CF objects we have created - we don't need them any more
CFRelease(aggDeviceDict);
CFRelease(subDevicesArray);
// release the device UID
for (size_t i = 0; i < captureDeviceUID.size(); ++i) {
CFRelease(captureDeviceUID[i]);
}
for (size_t i = 0; i < playbackDeviceUID.size(); ++i) {
CFRelease(playbackDeviceUID[i]);
}
#ifndef NDEBUG
printf("AggregateDevice: new aggregate device %u\n", *created_device);
#endif
return 0;
error:
destroy_aggregate_device();
return -1;
}

View File

@ -16,6 +16,7 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <sstream>
#include "coremidi_io.h"
#include <CoreAudio/HostTime.h>
@ -25,12 +26,16 @@ static void notifyProc (const MIDINotification *message, void *refCon) {
}
static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
// TODO skip while freewheeling
CoreMidiIo *self = static_cast<CoreMidiIo*> (procRef);
if (!self || !self->enabled()) {
// skip while freewheeling
return;
}
RingBuffer<uint8_t> * rb = static_cast<RingBuffer < uint8_t > *> (srcRef);
if (!rb) return;
for (UInt32 i = 0; i < list->numPackets; i++) {
const MIDIPacket *packet = &list->packet[i];
if (rb->write_space() < sizeof(MIDIPacket)) {
if (rb->write_space() < sizeof(MIDIPacket)) {
fprintf(stderr, "CoreMIDI: dropped MIDI event\n");
continue;
}
@ -38,8 +43,19 @@ static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *s
}
}
static std::string getDisplayName(MIDIObjectRef object)
{
CFStringRef name = nil;
if (noErr != MIDIObjectGetStringProperty(object, kMIDIPropertyDisplayName, &name) && name) {
return "";
}
std::string rv (CFStringGetCStringPtr(name, kCFStringEncodingUTF8));
CFRelease(name);
CoreMidiIo::CoreMidiIo()
return rv;
}
CoreMidiIo::CoreMidiIo()
: _midi_client (0)
, _input_endpoints (0)
, _output_endpoints (0)
@ -51,25 +67,28 @@ CoreMidiIo::CoreMidiIo()
, _n_midi_out (0)
, _time_at_cycle_start (0)
, _active (false)
, _enabled (true)
, _run (false)
, _changed_callback (0)
, _changed_arg (0)
{
OSStatus err;
err = MIDIClientCreate(CFSTR("Ardour"), &notifyProc, this, &_midi_client);
if (noErr != err) {
fprintf(stderr, "Creating Midi Client failed\n");
}
pthread_mutex_init (&_discovery_lock, 0);
}
CoreMidiIo::~CoreMidiIo()
CoreMidiIo::~CoreMidiIo()
{
pthread_mutex_lock (&_discovery_lock);
cleanup();
MIDIClientDispose(_midi_client); _midi_client = 0;
if (_midi_client) {
MIDIClientDispose(_midi_client);
_midi_client = 0;
}
pthread_mutex_unlock (&_discovery_lock);
pthread_mutex_destroy (&_discovery_lock);
}
void
CoreMidiIo::cleanup()
CoreMidiIo::cleanup()
{
_active = false;
for (uint32_t i = 0 ; i < _n_midi_in ; ++i) {
@ -93,13 +112,13 @@ CoreMidiIo::cleanup()
}
void
CoreMidiIo::start_cycle()
CoreMidiIo::start_cycle()
{
_time_at_cycle_start = AudioGetCurrentHostTime();
}
void
CoreMidiIo::notify_proc(const MIDINotification *message)
CoreMidiIo::notify_proc(const MIDINotification *message)
{
switch(message->messageID) {
case kMIDIMsgSetupChanged:
@ -150,7 +169,7 @@ CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uin
MIDIPacket packet;
size_t rv = _rb[port]->read((uint8_t*)&packet, sizeof(MIDIPacket));
assert(rv == sizeof(MIDIPacket));
_input_queue[port].push_back(boost::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket (&packet)));
_input_queue[port].push_back(boost::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket (&packet)));
}
UInt64 start = _time_at_cycle_start;
@ -208,12 +227,68 @@ CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, cons
return 0;
}
void
CoreMidiIo::discover()
std::string
CoreMidiIo::port_name (uint32_t port, bool input)
{
cleanup();
std::stringstream ss;
std::string pn;
// XXX including the number will not yield persistent port-names
// when disconnecting devices in the middle.
if (input) {
ss << "system:midi_capture_" << port;
if (port < _n_midi_in) {
pn = getDisplayName(_input_endpoints[port]);
}
} else {
ss << "system:midi_playback_" << port;
if (port < _n_midi_out) {
pn = getDisplayName(_output_endpoints[port]);
}
}
if (!pn.empty()) {
ss << " - " << pn;
}
return ss.str();
}
assert(!_active && _midi_client);
void
CoreMidiIo::start () {
_run = true;
if (!_midi_client) {
OSStatus err;
err = MIDIClientCreate(CFSTR("Ardour"), &notifyProc, this, &_midi_client);
if (noErr != err) {
fprintf(stderr, "Creating Midi Client failed\n");
}
}
discover();
}
void
CoreMidiIo::stop ()
{
_run = false;
pthread_mutex_lock (&_discovery_lock);
cleanup();
pthread_mutex_unlock (&_discovery_lock);
#if 0
if (_midi_client) {
MIDIClientDispose(_midi_client);
_midi_client = 0;
}
#endif
}
void
CoreMidiIo::discover()
{
if (!_run || !_midi_client) { return; }
if (pthread_mutex_trylock (&_discovery_lock)) {
return;
}
cleanup();
ItemCount srcCount = MIDIGetNumberOfSources();
ItemCount dstCount = MIDIGetNumberOfDestinations();
@ -232,16 +307,19 @@ CoreMidiIo::discover()
for (ItemCount i = 0; i < srcCount; i++) {
OSStatus err;
MIDIEndpointRef src = MIDIGetSource(i);
if (!src) continue;
#ifndef NDEBUG
printf("MIDI IN DEVICE: %s\n", getDisplayName(src).c_str());
#endif
CFStringRef port_name;
port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_capture_%lu"), i);
err = MIDIInputPortCreate (_midi_client, port_name, midiInputCallback, this, &_input_ports[_n_midi_in]);
if (noErr != err) {
fprintf(stderr, "Cannot create Midi Output\n");
// TODO handle errors
continue;
}
// TODO get device name/ID
_rb[_n_midi_in] = new RingBuffer<uint8_t>(1024 * sizeof(MIDIPacket));
_input_queue[_n_midi_in] = CoreMIDIQueue();
MIDIPortConnectSource(_input_ports[_n_midi_in], src, (void*) _rb[_n_midi_in]);
@ -259,10 +337,13 @@ CoreMidiIo::discover()
err = MIDIOutputPortCreate (_midi_client, port_name, &_output_ports[_n_midi_out]);
if (noErr != err) {
fprintf(stderr, "Cannot create Midi Output\n");
// TODO handle errors
continue;
}
// TODO get device name/ID
#ifndef NDEBUG
printf("MIDI OUT DEVICE: %s\n", getDisplayName(dst).c_str());
#endif
MIDIPortConnectSource(_output_ports[_n_midi_out], dst, NULL);
CFRelease(port_name);
_output_endpoints[_n_midi_out] = dst;
@ -274,4 +355,5 @@ CoreMidiIo::discover()
}
_active = true;
pthread_mutex_unlock (&_discovery_lock);
}

View File

@ -64,8 +64,9 @@ public:
CoreMidiIo (void);
~CoreMidiIo (void);
// TODO explicit start/stop, add/remove devices as needed.
void discover ();
void start ();
void stop ();
void start_cycle ();
int send_event (uint32_t, double, const uint8_t *, const size_t);
@ -73,15 +74,20 @@ public:
uint32_t n_midi_inputs (void) const { return _n_midi_in; }
uint32_t n_midi_outputs (void) const { return _n_midi_out; }
std::string port_name (uint32_t, bool input);
void notify_proc (const MIDINotification *message);
void set_enabled (bool yn = true) { _enabled = yn; }
bool enabled (void) const { return _active && _enabled; }
void set_port_changed_callback (void (changed_callback (void*)), void *arg) {
_changed_callback = changed_callback;
_changed_arg = arg;
}
private:
void discover ();
void cleanup ();
MIDIClientRef _midi_client;
@ -97,8 +103,12 @@ private:
uint32_t _n_midi_out;
MIDITimeStamp _time_at_cycle_start;
bool _active;
bool _active; // internal deactivate during discovery etc
bool _enabled; // temporary disable, e.g. during freewheeli
bool _run; // general status
void (* _changed_callback) (void*);
void * _changed_arg;
pthread_mutex_t _discovery_lock;
};

View File

@ -32,7 +32,7 @@ def build(bld):
obj.framework = [ 'CoreAudio', 'AudioToolbox', 'CoreServices', 'CoreMidi' ]
obj.install_path = os.path.join(bld.env['LIBDIR'], 'backends')
obj.defines = ['PACKAGE="' + I18N_PACKAGE + '"',
'ARDOURBACKEND_DLL_EXPORTS', 'COREAUDIO_108'
'ARDOURBACKEND_DLL_EXPORTS'
]
# use new coreaudio API (the old one was deprecated in 10.6, yet still works)