793 lines
25 KiB
C++
793 lines
25 KiB
C++
/**
|
|
* @author Gary P. SCAVONE
|
|
*
|
|
* @copyright 2001-2013 Gary P. Scavone, all right reserved
|
|
*
|
|
* @license like MIT (see license file)
|
|
*/
|
|
|
|
|
|
#if defined(__UNIX_JACK__)
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
#include <iostream>
|
|
#include <airtaudio/Interface.h>
|
|
#include <string.h>
|
|
|
|
// JACK is a low-latency audio server, originally written for the
|
|
// GNU/Linux operating system and now also ported to OS-X. It can
|
|
// connect a number of different applications to an audio device, as
|
|
// well as allowing them to share audio between themselves.
|
|
//
|
|
// When using JACK with RtAudio, "devices" refer to JACK clients that
|
|
// have ports connected to the server. The JACK server is typically
|
|
// started in a terminal as follows:
|
|
//
|
|
// .jackd -d alsa -d hw:0
|
|
//
|
|
// or through an interface program such as qjackctl. Many of the
|
|
// parameters normally set for a stream are fixed by the JACK server
|
|
// and can be specified when the JACK server is started. In
|
|
// particular,
|
|
//
|
|
// .jackd -d alsa -d hw:0 -r 44100 -p 512 -n 4
|
|
//
|
|
// specifies a sample rate of 44100 Hz, a buffer size of 512 sample
|
|
// frames, and number of buffers = 4. Once the server is running, it
|
|
// is not possible to override these values. If the values are not
|
|
// specified in the command-line, the JACK server uses default values.
|
|
//
|
|
// The JACK server does not have to be running when an instance of
|
|
// RtApiJack is created, though the function getDeviceCount() will
|
|
// report 0 devices found until JACK has been started. When no
|
|
// devices are available (i.e., the JACK server is not running), a
|
|
// stream cannot be opened.
|
|
|
|
#include <jack/jack.h>
|
|
#include <unistd.h>
|
|
#include <cstdio>
|
|
|
|
// A structure to hold various information related to the Jack API
|
|
// implementation.
|
|
struct JackHandle {
|
|
jack_client_t *client;
|
|
jack_port_t **ports[2];
|
|
std::string deviceName[2];
|
|
bool xrun[2];
|
|
std::condition_variable condition;
|
|
int32_t drainCounter; // Tracks callback counts when draining
|
|
bool internalDrain; // Indicates if stop is initiated from callback or not.
|
|
|
|
JackHandle(void) :
|
|
client(0),
|
|
drainCounter(0),
|
|
internalDrain(false) {
|
|
ports[0] = 0;
|
|
ports[1] = 0;
|
|
xrun[0] = false;
|
|
xrun[1] = false;
|
|
}
|
|
};
|
|
|
|
static void jackSilentError(const char *) {};
|
|
|
|
airtaudio::api::Jack::Jack(void) {
|
|
// Nothing to do here.
|
|
#if !defined(__RTAUDIO_DEBUG__)
|
|
// Turn off Jack's internal error reporting.
|
|
jack_set_error_function(&jackSilentError);
|
|
#endif
|
|
}
|
|
|
|
airtaudio::api::Jack::~Jack(void) {
|
|
if (m_stream.state != STREAM_CLOSED) {
|
|
closeStream();
|
|
}
|
|
}
|
|
|
|
uint32_t airtaudio::api::Jack::getDeviceCount(void) {
|
|
// See if we can become a jack client.
|
|
jack_options_t options = (jack_options_t) (JackNoStartServer); //JackNullOption;
|
|
jack_status_t *status = NULL;
|
|
jack_client_t *client = jack_client_open("RtApiJackCount", options, status);
|
|
if (client == NULL) {
|
|
return 0;
|
|
}
|
|
const char **ports;
|
|
std::string port, previousPort;
|
|
uint32_t nChannels = 0, nDevices = 0;
|
|
ports = jack_get_ports(client, NULL, NULL, 0);
|
|
if (ports) {
|
|
// Parse the port names up to the first colon (:).
|
|
size_t iColon = 0;
|
|
do {
|
|
port = (char *) ports[ nChannels ];
|
|
iColon = port.find(":");
|
|
if (iColon != std::string::npos) {
|
|
port = port.substr(0, iColon + 1);
|
|
if (port != previousPort) {
|
|
nDevices++;
|
|
previousPort = port;
|
|
}
|
|
}
|
|
} while (ports[++nChannels]);
|
|
free(ports);
|
|
}
|
|
jack_client_close(client);
|
|
return nDevices;
|
|
}
|
|
|
|
airtaudio::DeviceInfo airtaudio::api::Jack::getDeviceInfo(uint32_t _device)
|
|
{
|
|
airtaudio::DeviceInfo info;
|
|
info.probed = false;
|
|
jack_options_t options = (jack_options_t) (JackNoStartServer); //JackNullOption
|
|
jack_status_t *status = NULL;
|
|
jack_client_t *client = jack_client_open("RtApiJackInfo", options, status);
|
|
if (client == NULL) {
|
|
m_errorText = "airtaudio::api::Jack::getDeviceInfo: Jack server not found or connection error!";
|
|
error(airtaudio::errorWarning);
|
|
return info;
|
|
}
|
|
const char **ports;
|
|
std::string port, previousPort;
|
|
uint32_t nPorts = 0, nDevices = 0;
|
|
ports = jack_get_ports(client, NULL, NULL, 0);
|
|
if (ports) {
|
|
// Parse the port names up to the first colon (:).
|
|
size_t iColon = 0;
|
|
do {
|
|
port = (char *) ports[ nPorts ];
|
|
iColon = port.find(":");
|
|
if (iColon != std::string::npos) {
|
|
port = port.substr(0, iColon);
|
|
if (port != previousPort) {
|
|
if (nDevices == _device) {
|
|
info.name = port;
|
|
}
|
|
nDevices++;
|
|
previousPort = port;
|
|
}
|
|
}
|
|
} while (ports[++nPorts]);
|
|
free(ports);
|
|
}
|
|
|
|
if (_device >= nDevices) {
|
|
jack_client_close(client);
|
|
m_errorText = "airtaudio::api::Jack::getDeviceInfo: device ID is invalid!";
|
|
error(airtaudio::errorInvalidUse);
|
|
return info;
|
|
}
|
|
|
|
// Get the current jack server sample rate.
|
|
info.sampleRates.clear();
|
|
info.sampleRates.push_back(jack_get_sample_rate(client));
|
|
|
|
// Count the available ports containing the client name as device
|
|
// channels. Jack "input ports" equal RtAudio output channels.
|
|
uint32_t nChannels = 0;
|
|
ports = jack_get_ports(client, info.name.c_str(), NULL, JackPortIsInput);
|
|
if (ports) {
|
|
while (ports[ nChannels ]) {
|
|
nChannels++;
|
|
}
|
|
free(ports);
|
|
info.outputChannels = nChannels;
|
|
}
|
|
// Jack "output ports" equal RtAudio input channels.
|
|
nChannels = 0;
|
|
ports = jack_get_ports(client, info.name.c_str(), NULL, JackPortIsOutput);
|
|
if (ports) {
|
|
while (ports[ nChannels ]) {
|
|
nChannels++;
|
|
}
|
|
free(ports);
|
|
info.inputChannels = nChannels;
|
|
}
|
|
if (info.outputChannels == 0 && info.inputChannels == 0) {
|
|
jack_client_close(client);
|
|
m_errorText = "airtaudio::api::Jack::getDeviceInfo: error determining Jack input/output channels!";
|
|
error(airtaudio::errorWarning);
|
|
return info;
|
|
}
|
|
// If device opens for both playback and capture, we determine the channels.
|
|
if (info.outputChannels > 0 && info.inputChannels > 0) {
|
|
info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;
|
|
}
|
|
// Jack always uses 32-bit floats.
|
|
info.nativeFormats = airtaudio::FLOAT32;
|
|
// Jack doesn't provide default devices so we'll use the first available one.
|
|
if ( _device == 0
|
|
&& info.outputChannels > 0) {
|
|
info.isDefaultOutput = true;
|
|
}
|
|
if ( _device == 0
|
|
&& info.inputChannels > 0) {
|
|
info.isDefaultInput = true;
|
|
}
|
|
jack_client_close(client);
|
|
info.probed = true;
|
|
return info;
|
|
}
|
|
|
|
static int32_t jackCallbackHandler(jack_nframes_t _nframes, void *_infoPointer) {
|
|
airtaudio::CallbackInfo* info = (airtaudio::CallbackInfo*)_infoPointer;
|
|
airtaudio::api::Jack* object = (airtaudio::api::Jack*)info->object;
|
|
if (object->callbackEvent((uint64_t)_nframes) == false) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// This function will be called by a spawned thread when the Jack
|
|
// server signals that it is shutting down. It is necessary to handle
|
|
// it this way because the jackShutdown() function must return before
|
|
// the jack_deactivate() function (in closeStream()) will return.
|
|
static void *jackCloseStream(void *_ptr) {
|
|
airtaudio::CallbackInfo* info = (airtaudio::CallbackInfo*)_ptr;
|
|
airtaudio::api::Jack* object = (airtaudio::api::Jack*)info->object;
|
|
object->closeStream();
|
|
pthread_exit(NULL);
|
|
}
|
|
|
|
static void jackShutdown(void* _infoPointer) {
|
|
airtaudio::CallbackInfo* info = (airtaudio::CallbackInfo*)_infoPointer;
|
|
airtaudio::api::Jack* object = (airtaudio::api::Jack*)info->object;
|
|
// Check current stream state. If stopped, then we'll assume this
|
|
// was called as a result of a call to airtaudio::api::Jack::stopStream (the
|
|
// deactivation of a client handle causes this function to be called).
|
|
// If not, we'll assume the Jack server is shutting down or some
|
|
// other problem occurred and we should close the stream.
|
|
if (object->isStreamRunning() == false) {
|
|
return;
|
|
}
|
|
pthread_t threadId;
|
|
pthread_create(&threadId, NULL, jackCloseStream, info);
|
|
std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl;
|
|
}
|
|
|
|
static int32_t jackXrun(void* _infoPointer) {
|
|
JackHandle* handle = (JackHandle*)_infoPointer;
|
|
if (handle->ports[0]) {
|
|
handle->xrun[0] = true;
|
|
}
|
|
if (handle->ports[1]) {
|
|
handle->xrun[1] = true;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool airtaudio::api::Jack::probeDeviceOpen(uint32_t device,
|
|
airtaudio::api::StreamMode mode,
|
|
uint32_t channels,
|
|
uint32_t firstChannel,
|
|
uint32_t sampleRate,
|
|
airtaudio::format format,
|
|
uint32_t *bufferSize,
|
|
airtaudio::StreamOptions *options) {
|
|
JackHandle *handle = (JackHandle *) m_stream.apiHandle;
|
|
// Look for jack server and try to become a client (only do once per stream).
|
|
jack_client_t *client = 0;
|
|
if (mode == OUTPUT || (mode == INPUT && m_stream.mode != OUTPUT)) {
|
|
jack_options_t jackoptions = (jack_options_t) (JackNoStartServer); //JackNullOption;
|
|
jack_status_t *status = NULL;
|
|
if (options && !options->streamName.empty()) {
|
|
client = jack_client_open(options->streamName.c_str(), jackoptions, status);
|
|
} else {
|
|
client = jack_client_open("RtApiJack", jackoptions, status);
|
|
}
|
|
if (client == 0) {
|
|
m_errorText = "airtaudio::api::Jack::probeDeviceOpen: Jack server not found or connection error!";
|
|
error(airtaudio::errorWarning);
|
|
return FAILURE;
|
|
}
|
|
}
|
|
else {
|
|
// The handle must have been created on an earlier pass.
|
|
client = handle->client;
|
|
}
|
|
|
|
const char **ports;
|
|
std::string port, previousPort, deviceName;
|
|
uint32_t nPorts = 0, nDevices = 0;
|
|
ports = jack_get_ports(client, NULL, NULL, 0);
|
|
if (ports) {
|
|
// Parse the port names up to the first colon (:).
|
|
size_t iColon = 0;
|
|
do {
|
|
port = (char *) ports[ nPorts ];
|
|
iColon = port.find(":");
|
|
if (iColon != std::string::npos) {
|
|
port = port.substr(0, iColon);
|
|
if (port != previousPort) {
|
|
if (nDevices == device) deviceName = port;
|
|
nDevices++;
|
|
previousPort = port;
|
|
}
|
|
}
|
|
} while (ports[++nPorts]);
|
|
free(ports);
|
|
}
|
|
|
|
if (device >= nDevices) {
|
|
m_errorText = "airtaudio::api::Jack::probeDeviceOpen: device ID is invalid!";
|
|
return FAILURE;
|
|
}
|
|
|
|
// Count the available ports containing the client name as device
|
|
// channels. Jack "input ports" equal RtAudio output channels.
|
|
uint32_t nChannels = 0;
|
|
uint64_t flag = JackPortIsInput;
|
|
if (mode == INPUT) flag = JackPortIsOutput;
|
|
ports = jack_get_ports(client, deviceName.c_str(), NULL, flag);
|
|
if (ports) {
|
|
while (ports[ nChannels ]) nChannels++;
|
|
free(ports);
|
|
}
|
|
|
|
// Compare the jack ports for specified client to the requested number of channels.
|
|
if (nChannels < (channels + firstChannel)) {
|
|
m_errorStream << "airtaudio::api::Jack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ").";
|
|
m_errorText = m_errorStream.str();
|
|
return FAILURE;
|
|
}
|
|
|
|
// Check the jack server sample rate.
|
|
uint32_t jackRate = jack_get_sample_rate(client);
|
|
if (sampleRate != jackRate) {
|
|
jack_client_close(client);
|
|
m_errorStream << "airtaudio::api::Jack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ").";
|
|
m_errorText = m_errorStream.str();
|
|
return FAILURE;
|
|
}
|
|
m_stream.sampleRate = jackRate;
|
|
|
|
// Get the latency of the JACK port.
|
|
ports = jack_get_ports(client, deviceName.c_str(), NULL, flag);
|
|
if (ports[ firstChannel ]) {
|
|
// Added by Ge Wang
|
|
jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency);
|
|
// the range (usually the min and max are equal)
|
|
jack_latency_range_t latrange; latrange.min = latrange.max = 0;
|
|
// get the latency range
|
|
jack_port_get_latency_range(jack_port_by_name(client, ports[firstChannel]), cbmode, &latrange);
|
|
// be optimistic, use the min!
|
|
m_stream.latency[mode] = latrange.min;
|
|
//m_stream.latency[mode] = jack_port_get_latency(jack_port_by_name(client, ports[ firstChannel ]));
|
|
}
|
|
free(ports);
|
|
|
|
// The jack server always uses 32-bit floating-point data.
|
|
m_stream.deviceFormat[mode] = FLOAT32;
|
|
m_stream.userFormat = format;
|
|
|
|
if (options && options->flags & NONINTERLEAVED) m_stream.userInterleaved = false;
|
|
else m_stream.userInterleaved = true;
|
|
|
|
// Jack always uses non-interleaved buffers.
|
|
m_stream.deviceInterleaved[mode] = false;
|
|
|
|
// Jack always provides host byte-ordered data.
|
|
m_stream.doByteSwap[mode] = false;
|
|
|
|
// Get the buffer size. The buffer size and number of buffers
|
|
// (periods) is set when the jack server is started.
|
|
m_stream.bufferSize = (int) jack_get_buffer_size(client);
|
|
*bufferSize = m_stream.bufferSize;
|
|
|
|
m_stream.nDeviceChannels[mode] = channels;
|
|
m_stream.nUserChannels[mode] = channels;
|
|
|
|
// Set flags for buffer conversion.
|
|
m_stream.doConvertBuffer[mode] = false;
|
|
if (m_stream.userFormat != m_stream.deviceFormat[mode])
|
|
m_stream.doConvertBuffer[mode] = true;
|
|
if (m_stream.userInterleaved != m_stream.deviceInterleaved[mode] &&
|
|
m_stream.nUserChannels[mode] > 1)
|
|
m_stream.doConvertBuffer[mode] = true;
|
|
|
|
// Allocate our JackHandle structure for the stream.
|
|
if (handle == 0) {
|
|
try {
|
|
handle = new JackHandle;
|
|
}
|
|
catch (std::bad_alloc&) {
|
|
m_errorText = "airtaudio::api::Jack::probeDeviceOpen: error allocating JackHandle memory.";
|
|
goto error;
|
|
}
|
|
m_stream.apiHandle = (void *) handle;
|
|
handle->client = client;
|
|
}
|
|
handle->deviceName[mode] = deviceName;
|
|
|
|
// Allocate necessary internal buffers.
|
|
uint64_t bufferBytes;
|
|
bufferBytes = m_stream.nUserChannels[mode] * *bufferSize * formatBytes(m_stream.userFormat);
|
|
m_stream.userBuffer[mode] = (char *) calloc(bufferBytes, 1);
|
|
if (m_stream.userBuffer[mode] == NULL) {
|
|
m_errorText = "airtaudio::api::Jack::probeDeviceOpen: error allocating user buffer memory.";
|
|
goto error;
|
|
}
|
|
|
|
if (m_stream.doConvertBuffer[mode]) {
|
|
|
|
bool makeBuffer = true;
|
|
if (mode == OUTPUT)
|
|
bufferBytes = m_stream.nDeviceChannels[0] * formatBytes(m_stream.deviceFormat[0]);
|
|
else { // mode == INPUT
|
|
bufferBytes = m_stream.nDeviceChannels[1] * formatBytes(m_stream.deviceFormat[1]);
|
|
if (m_stream.mode == OUTPUT && m_stream.deviceBuffer) {
|
|
uint64_t bytesOut = m_stream.nDeviceChannels[0] * formatBytes(m_stream.deviceFormat[0]);
|
|
if (bufferBytes < bytesOut) makeBuffer = false;
|
|
}
|
|
}
|
|
|
|
if (makeBuffer) {
|
|
bufferBytes *= *bufferSize;
|
|
if (m_stream.deviceBuffer) free(m_stream.deviceBuffer);
|
|
m_stream.deviceBuffer = (char *) calloc(bufferBytes, 1);
|
|
if (m_stream.deviceBuffer == NULL) {
|
|
m_errorText = "airtaudio::api::Jack::probeDeviceOpen: error allocating device buffer memory.";
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allocate memory for the Jack ports (channels) identifiers.
|
|
handle->ports[mode] = (jack_port_t **) malloc (sizeof (jack_port_t *) * channels);
|
|
if (handle->ports[mode] == NULL) {
|
|
m_errorText = "airtaudio::api::Jack::probeDeviceOpen: error allocating port memory.";
|
|
goto error;
|
|
}
|
|
|
|
m_stream.device[mode] = device;
|
|
m_stream.channelOffset[mode] = firstChannel;
|
|
m_stream.state = STREAM_STOPPED;
|
|
m_stream.callbackInfo.object = (void *) this;
|
|
|
|
if (m_stream.mode == OUTPUT && mode == INPUT)
|
|
// We had already set up the stream for output.
|
|
m_stream.mode = DUPLEX;
|
|
else {
|
|
m_stream.mode = mode;
|
|
jack_set_process_callback(handle->client, jackCallbackHandler, (void *) &m_stream.callbackInfo);
|
|
jack_set_xrun_callback(handle->client, jackXrun, (void *) &handle);
|
|
jack_on_shutdown(handle->client, jackShutdown, (void *) &m_stream.callbackInfo);
|
|
}
|
|
|
|
// Register our ports.
|
|
char label[64];
|
|
if (mode == OUTPUT) {
|
|
for (uint32_t i=0; i<m_stream.nUserChannels[0]; i++) {
|
|
snprintf(label, 64, "outport %d", i);
|
|
handle->ports[0][i] = jack_port_register(handle->client,
|
|
(const char *)label,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsOutput,
|
|
0);
|
|
}
|
|
} else {
|
|
for (uint32_t i=0; i<m_stream.nUserChannels[1]; i++) {
|
|
snprintf(label, 64, "inport %d", i);
|
|
handle->ports[1][i] = jack_port_register(handle->client,
|
|
(const char *)label,
|
|
JACK_DEFAULT_AUDIO_TYPE,
|
|
JackPortIsInput,
|
|
0);
|
|
}
|
|
}
|
|
|
|
// Setup the buffer conversion information structure. We don't use
|
|
// buffers to do channel offsets, so we override that parameter
|
|
// here.
|
|
if (m_stream.doConvertBuffer[mode]) {
|
|
setConvertInfo(mode, 0);
|
|
}
|
|
return SUCCESS;
|
|
|
|
error:
|
|
if (handle) {
|
|
jack_client_close(handle->client);
|
|
if (handle->ports[0]) {
|
|
free(handle->ports[0]);
|
|
}
|
|
if (handle->ports[1]) {
|
|
free(handle->ports[1]);
|
|
}
|
|
delete handle;
|
|
m_stream.apiHandle = 0;
|
|
}
|
|
|
|
for (int32_t iii=0; iii<2; ++iii) {
|
|
if (m_stream.userBuffer[iii]) {
|
|
free(m_stream.userBuffer[iii]);
|
|
m_stream.userBuffer[iii] = 0;
|
|
}
|
|
}
|
|
|
|
if (m_stream.deviceBuffer) {
|
|
free(m_stream.deviceBuffer);
|
|
m_stream.deviceBuffer = 0;
|
|
}
|
|
|
|
return FAILURE;
|
|
}
|
|
|
|
void airtaudio::api::Jack::closeStream(void)
|
|
{
|
|
if (m_stream.state == STREAM_CLOSED) {
|
|
m_errorText = "airtaudio::api::Jack::closeStream(): no open stream to close!";
|
|
error(airtaudio::errorWarning);
|
|
return;
|
|
}
|
|
|
|
JackHandle *handle = (JackHandle *) m_stream.apiHandle;
|
|
if (handle) {
|
|
|
|
if (m_stream.state == STREAM_RUNNING)
|
|
jack_deactivate(handle->client);
|
|
|
|
jack_client_close(handle->client);
|
|
}
|
|
|
|
if (handle) {
|
|
if (handle->ports[0]) {
|
|
free(handle->ports[0]);
|
|
}
|
|
if (handle->ports[1]) {
|
|
free(handle->ports[1]);
|
|
}
|
|
delete handle;
|
|
m_stream.apiHandle = 0;
|
|
}
|
|
|
|
for (int32_t i=0; i<2; i++) {
|
|
if (m_stream.userBuffer[i]) {
|
|
free(m_stream.userBuffer[i]);
|
|
m_stream.userBuffer[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (m_stream.deviceBuffer) {
|
|
free(m_stream.deviceBuffer);
|
|
m_stream.deviceBuffer = 0;
|
|
}
|
|
|
|
m_stream.mode = UNINITIALIZED;
|
|
m_stream.state = STREAM_CLOSED;
|
|
}
|
|
|
|
void airtaudio::api::Jack::startStream(void)
|
|
{
|
|
verifyStream();
|
|
if (m_stream.state == STREAM_RUNNING) {
|
|
m_errorText = "airtaudio::api::Jack::startStream(): the stream is already running!";
|
|
error(airtaudio::errorWarning);
|
|
return;
|
|
}
|
|
|
|
JackHandle *handle = (JackHandle *) m_stream.apiHandle;
|
|
int32_t result = jack_activate(handle->client);
|
|
if (result) {
|
|
m_errorText = "airtaudio::api::Jack::startStream(): unable to activate JACK client!";
|
|
goto unlock;
|
|
}
|
|
|
|
const char **ports;
|
|
|
|
// Get the list of available ports.
|
|
if (m_stream.mode == OUTPUT || m_stream.mode == DUPLEX) {
|
|
result = 1;
|
|
ports = jack_get_ports(handle->client, handle->deviceName[0].c_str(), NULL, JackPortIsInput);
|
|
if (ports == NULL) {
|
|
m_errorText = "airtaudio::api::Jack::startStream(): error determining available JACK input ports!";
|
|
goto unlock;
|
|
}
|
|
|
|
// Now make the port connections. Since RtAudio wasn't designed to
|
|
// allow the user to select particular channels of a device, we'll
|
|
// just open the first "nChannels" ports with offset.
|
|
for (uint32_t i=0; i<m_stream.nUserChannels[0]; i++) {
|
|
result = 1;
|
|
if (ports[ m_stream.channelOffset[0] + i ])
|
|
result = jack_connect(handle->client, jack_port_name(handle->ports[0][i]), ports[ m_stream.channelOffset[0] + i ]);
|
|
if (result) {
|
|
free(ports);
|
|
m_errorText = "airtaudio::api::Jack::startStream(): error connecting output ports!";
|
|
goto unlock;
|
|
}
|
|
}
|
|
free(ports);
|
|
}
|
|
|
|
if (m_stream.mode == INPUT || m_stream.mode == DUPLEX) {
|
|
result = 1;
|
|
ports = jack_get_ports(handle->client, handle->deviceName[1].c_str(), NULL, JackPortIsOutput);
|
|
if (ports == NULL) {
|
|
m_errorText = "airtaudio::api::Jack::startStream(): error determining available JACK output ports!";
|
|
goto unlock;
|
|
}
|
|
|
|
// Now make the port connections. See note above.
|
|
for (uint32_t i=0; i<m_stream.nUserChannels[1]; i++) {
|
|
result = 1;
|
|
if (ports[ m_stream.channelOffset[1] + i ])
|
|
result = jack_connect(handle->client, ports[ m_stream.channelOffset[1] + i ], jack_port_name(handle->ports[1][i]));
|
|
if (result) {
|
|
free(ports);
|
|
m_errorText = "airtaudio::api::Jack::startStream(): error connecting input ports!";
|
|
goto unlock;
|
|
}
|
|
}
|
|
free(ports);
|
|
}
|
|
|
|
handle->drainCounter = 0;
|
|
handle->internalDrain = false;
|
|
m_stream.state = STREAM_RUNNING;
|
|
|
|
unlock:
|
|
if (result == 0) return;
|
|
error(airtaudio::errorSystemError);
|
|
}
|
|
|
|
void airtaudio::api::Jack::stopStream(void) {
|
|
verifyStream();
|
|
if (m_stream.state == STREAM_STOPPED) {
|
|
m_errorText = "airtaudio::api::Jack::stopStream(): the stream is already stopped!";
|
|
error(airtaudio::errorWarning);
|
|
return;
|
|
}
|
|
JackHandle *handle = (JackHandle *) m_stream.apiHandle;
|
|
if ( m_stream.mode == OUTPUT
|
|
|| m_stream.mode == DUPLEX) {
|
|
if (handle->drainCounter == 0) {
|
|
handle->drainCounter = 2;
|
|
std::unique_lock<std::mutex> lck(m_stream.mutex);
|
|
handle->condition.wait(lck);
|
|
}
|
|
}
|
|
jack_deactivate(handle->client);
|
|
m_stream.state = STREAM_STOPPED;
|
|
}
|
|
|
|
void airtaudio::api::Jack::abortStream(void)
|
|
{
|
|
verifyStream();
|
|
if (m_stream.state == STREAM_STOPPED) {
|
|
m_errorText = "airtaudio::api::Jack::abortStream(): the stream is already stopped!";
|
|
error(airtaudio::errorWarning);
|
|
return;
|
|
}
|
|
|
|
JackHandle *handle = (JackHandle *) m_stream.apiHandle;
|
|
handle->drainCounter = 2;
|
|
|
|
stopStream();
|
|
}
|
|
|
|
// This function will be called by a spawned thread when the user
|
|
// callback function signals that the stream should be stopped or
|
|
// aborted. It is necessary to handle it this way because the
|
|
// callbackEvent() function must return before the jack_deactivate()
|
|
// function will return.
|
|
static void jackStopStream(void *_ptr) {
|
|
airtaudio::CallbackInfo *info = (airtaudio::CallbackInfo *) _ptr;
|
|
airtaudio::api::Jack *object = (airtaudio::api::Jack *) info->object;
|
|
object->stopStream();
|
|
}
|
|
|
|
bool airtaudio::api::Jack::callbackEvent(uint64_t nframes)
|
|
{
|
|
if (m_stream.state == STREAM_STOPPED || m_stream.state == STREAM_STOPPING) return SUCCESS;
|
|
if (m_stream.state == STREAM_CLOSED) {
|
|
m_errorText = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!";
|
|
error(airtaudio::errorWarning);
|
|
return FAILURE;
|
|
}
|
|
if (m_stream.bufferSize != nframes) {
|
|
m_errorText = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!";
|
|
error(airtaudio::errorWarning);
|
|
return FAILURE;
|
|
}
|
|
|
|
CallbackInfo *info = (CallbackInfo *) &m_stream.callbackInfo;
|
|
JackHandle *handle = (JackHandle *) m_stream.apiHandle;
|
|
|
|
// Check if we were draining the stream and signal is finished.
|
|
if (handle->drainCounter > 3) {
|
|
m_stream.state = STREAM_STOPPING;
|
|
if (handle->internalDrain == true) {
|
|
new std::thread(jackStopStream, info);
|
|
} else {
|
|
handle->condition.notify_one();
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Invoke user callback first, to get fresh output data.
|
|
if (handle->drainCounter == 0) {
|
|
airtaudio::AirTAudioCallback callback = (airtaudio::AirTAudioCallback) info->callback;
|
|
double streamTime = getStreamTime();
|
|
airtaudio::streamStatus status = 0;
|
|
if (m_stream.mode != INPUT && handle->xrun[0] == true) {
|
|
status |= OUTPUT_UNDERFLOW;
|
|
handle->xrun[0] = false;
|
|
}
|
|
if (m_stream.mode != OUTPUT && handle->xrun[1] == true) {
|
|
status |= INPUT_OVERFLOW;
|
|
handle->xrun[1] = false;
|
|
}
|
|
int32_t cbReturnValue = callback(m_stream.userBuffer[0], m_stream.userBuffer[1],
|
|
m_stream.bufferSize, streamTime, status, info->userData);
|
|
if (cbReturnValue == 2) {
|
|
m_stream.state = STREAM_STOPPING;
|
|
handle->drainCounter = 2;
|
|
new std::thread(jackStopStream, info);
|
|
return SUCCESS;
|
|
}
|
|
else if (cbReturnValue == 1) {
|
|
handle->drainCounter = 1;
|
|
handle->internalDrain = true;
|
|
}
|
|
}
|
|
|
|
jack_default_audio_sample_t *jackbuffer;
|
|
uint64_t bufferBytes = nframes * sizeof(jack_default_audio_sample_t);
|
|
if (m_stream.mode == OUTPUT || m_stream.mode == DUPLEX) {
|
|
|
|
if (handle->drainCounter > 1) { // write zeros to the output stream
|
|
|
|
for (uint32_t i=0; i<m_stream.nDeviceChannels[0]; i++) {
|
|
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[0][i], (jack_nframes_t) nframes);
|
|
memset(jackbuffer, 0, bufferBytes);
|
|
}
|
|
|
|
}
|
|
else if (m_stream.doConvertBuffer[0]) {
|
|
|
|
convertBuffer(m_stream.deviceBuffer, m_stream.userBuffer[0], m_stream.convertInfo[0]);
|
|
|
|
for (uint32_t i=0; i<m_stream.nDeviceChannels[0]; i++) {
|
|
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[0][i], (jack_nframes_t) nframes);
|
|
memcpy(jackbuffer, &m_stream.deviceBuffer[i*bufferBytes], bufferBytes);
|
|
}
|
|
}
|
|
else { // no buffer conversion
|
|
for (uint32_t i=0; i<m_stream.nUserChannels[0]; i++) {
|
|
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[0][i], (jack_nframes_t) nframes);
|
|
memcpy(jackbuffer, &m_stream.userBuffer[0][i*bufferBytes], bufferBytes);
|
|
}
|
|
}
|
|
|
|
if (handle->drainCounter) {
|
|
handle->drainCounter++;
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
if ( m_stream.mode == INPUT
|
|
|| m_stream.mode == DUPLEX) {
|
|
if (m_stream.doConvertBuffer[1]) {
|
|
for (uint32_t i=0; i<m_stream.nDeviceChannels[1]; i++) {
|
|
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[1][i], (jack_nframes_t) nframes);
|
|
memcpy(&m_stream.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes);
|
|
}
|
|
convertBuffer(m_stream.userBuffer[1], m_stream.deviceBuffer, m_stream.convertInfo[1]);
|
|
} else {
|
|
// no buffer conversion
|
|
for (uint32_t i=0; i<m_stream.nUserChannels[1]; i++) {
|
|
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(handle->ports[1][i], (jack_nframes_t) nframes);
|
|
memcpy(&m_stream.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
unlock:
|
|
airtaudio::Api::tickStreamTime();
|
|
return SUCCESS;
|
|
}
|
|
//******************** End of __UNIX_JACK__ *********************//
|
|
#endif
|