732 lines
25 KiB
C++
Raw Normal View History

2015-02-09 21:44:32 +01:00
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
* @fork from RTAudio
*/
2015-02-08 15:09:39 +01:00
// must run before :
#if defined(__UNIX_JACK__)
#include <unistd.h>
#include <limits.h>
#include <iostream>
#include <airtaudio/Interface.h>
2014-03-12 23:55:49 +01:00
#include <airtaudio/debug.h>
#include <string.h>
2015-01-27 23:06:19 +01:00
#undef __class__
#define __class__ "api::Jack"
airtaudio::Api* airtaudio::api::Jack::Create() {
2014-03-11 22:37:22 +01:00
return new airtaudio::api::Jack();
}
// 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
2015-02-08 15:09:39 +01:00
// have ports connected to the server. The JACK server is typically
// started in a terminal as follows:
//
// .jackd -d alsa -d hw:0
//
2015-02-08 15:09:39 +01:00
// or through an interface program such as qjackctl. Many of the
// parameters normally set for a stream are fixed by the JACK server
2015-02-08 15:09:39 +01:00
// and can be specified when the JACK server is started. In
// particular,
//
2015-02-08 15:09:39 +01:00
// jackd -d alsa -d hw:0 -r 44100 -p 512 -n 4
// jackd -r -d alsa -r 48000
//
// specifies a sample rate of 44100 Hz, a buffer size of 512 sample
2015-02-08 15:09:39 +01:00
// 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
2015-02-09 21:44:32 +01:00
// airtaudio::Jack is created, though the function getDeviceCount() will
2015-02-08 15:09:39 +01:00
// 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>
namespace airtaudio {
namespace api {
class JackPrivate {
public:
jack_client_t *client;
jack_port_t **ports[2];
std::string deviceName[2];
bool xrun[2];
2015-02-24 22:20:11 +01:00
std11::condition_variable condition;
int32_t drainCounter; // Tracks callback counts when draining
bool internalDrain; // Indicates if stop is initiated from callback or not.
JackPrivate() :
client(0),
drainCounter(0),
internalDrain(false) {
ports[0] = 0;
ports[1] = 0;
xrun[0] = false;
xrun[1] = false;
}
};
}
}
airtaudio::api::Jack::Jack() :
m_private(new airtaudio::api::JackPrivate()) {
// Nothing to do here.
}
airtaudio::api::Jack::~Jack() {
2015-02-09 21:44:32 +01:00
if (m_state != airtaudio::state_closed) {
closeStream();
}
}
uint32_t airtaudio::api::Jack::getDeviceCount() {
// See if we can become a jack client.
jack_options_t options = (jack_options_t) (JackNoStartServer); //JackNullOption;
jack_status_t *status = nullptr;
2015-02-09 21:44:32 +01:00
jack_client_t *client = jack_client_open("airtaudioJackCount", options, status);
if (client == nullptr) {
return 0;
}
const char **ports;
std::string port, previousPort;
uint32_t nChannels = 0, nDevices = 0;
ports = jack_get_ports(client, nullptr, nullptr, 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;
}
2014-03-12 23:55:49 +01:00
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 = nullptr;
2015-02-09 21:44:32 +01:00
jack_client_t *client = jack_client_open("airtaudioJackInfo", options, status);
if (client == nullptr) {
ATA_ERROR("Jack server not found or connection error!");
2015-02-06 23:54:08 +01:00
// TODO : airtaudio::error_warning;
return info;
}
const char **ports;
std::string port, previousPort;
uint32_t nPorts = 0, nDevices = 0;
ports = jack_get_ports(client, nullptr, nullptr, 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);
ATA_ERROR("device ID is invalid!");
2015-02-06 23:54:08 +01:00
// TODO : airtaudio::error_invalidUse;
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(), nullptr, 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(), nullptr, JackPortIsOutput);
if (ports) {
while (ports[ nChannels ]) {
nChannels++;
}
free(ports);
info.inputChannels = nChannels;
}
if (info.outputChannels == 0 && info.inputChannels == 0) {
jack_client_close(client);
ATA_ERROR("error determining Jack input/output channels!");
2015-02-06 23:54:08 +01:00
// TODO : airtaudio::error_warning;
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.push_back(audio::format_float);
// 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;
}
2015-02-09 21:44:32 +01:00
int32_t airtaudio::api::Jack::jackCallbackHandler(jack_nframes_t _nframes, void* _userData) {
ATA_VERBOSE("Jack callback: [BEGIN] " << uint64_t(_userData));
airtaudio::api::Jack* myClass = reinterpret_cast<airtaudio::api::Jack*>(_userData);
if (myClass->callbackEvent((uint64_t)_nframes) == false) {
ATA_VERBOSE("Jack callback: [END] 1");
return 1;
}
2015-02-09 21:44:32 +01:00
ATA_VERBOSE("Jack callback: [END] 0");
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.
2015-02-09 21:44:32 +01:00
void airtaudio::api::Jack::jackCloseStream(void* _userData) {
2015-03-20 21:07:58 +01:00
etk::thread::setName("Jack_closeStream");
2015-02-09 21:44:32 +01:00
airtaudio::api::Jack* myClass = reinterpret_cast<airtaudio::api::Jack*>(_userData);
myClass->closeStream();
}
2015-02-09 21:44:32 +01:00
void airtaudio::api::Jack::jackShutdown(void* _userData) {
airtaudio::api::Jack* myClass = reinterpret_cast<airtaudio::api::Jack*>(_userData);
// 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.
2015-02-09 21:44:32 +01:00
if (myClass->isStreamRunning() == false) {
return;
}
2015-02-24 22:20:11 +01:00
new std11::thread(&airtaudio::api::Jack::jackCloseStream, _userData);
2015-02-09 21:44:32 +01:00
ATA_ERROR("The Jack server is shutting down this client ... stream stopped and closed!!");
}
2015-02-09 21:44:32 +01:00
int32_t airtaudio::api::Jack::jackXrun(void* _userData) {
airtaudio::api::Jack* myClass = reinterpret_cast<airtaudio::api::Jack*>(_userData);
if (myClass->m_private->ports[0]) {
myClass->m_private->xrun[0] = true;
}
if (myClass->m_private->ports[1]) {
myClass->m_private->xrun[1] = true;
}
return 0;
}
2014-03-12 23:55:49 +01:00
bool airtaudio::api::Jack::probeDeviceOpen(uint32_t _device,
2015-02-06 23:54:08 +01:00
airtaudio::mode _mode,
2014-03-12 23:55:49 +01:00
uint32_t _channels,
uint32_t _firstChannel,
uint32_t _sampleRate,
audio::format _format,
2014-03-12 23:55:49 +01:00
uint32_t* _bufferSize,
const airtaudio.::StreamOptions& _options) {
// Look for jack server and try to become a client (only do once per stream).
jack_client_t *client = 0;
2015-02-06 23:54:08 +01:00
if ( _mode == airtaudio::mode_output
|| ( _mode == airtaudio::mode_input
2015-02-09 21:44:32 +01:00
&& m_mode != airtaudio::mode_output)) {
jack_options_t jackoptions = (jack_options_t) (JackNoStartServer); //JackNullOption;
jack_status_t *status = nullptr;
if (!_options.streamName.empty()) {
client = jack_client_open(_options.streamName.c_str(), jackoptions, status);
} else {
2015-02-09 21:44:32 +01:00
client = jack_client_open("airtaudioJack", jackoptions, status);
}
if (client == 0) {
ATA_ERROR("Jack server not found or connection error!");
return false;
}
2014-03-12 23:55:49 +01:00
} else {
// The handle must have been created on an earlier pass.
client = m_private->client;
}
const char **ports;
std::string port, previousPort, deviceName;
uint32_t nPorts = 0, nDevices = 0;
ports = jack_get_ports(client, nullptr, nullptr, 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) {
2014-03-12 23:55:49 +01:00
if (nDevices == _device) {
deviceName = port;
}
nDevices++;
previousPort = port;
}
}
} while (ports[++nPorts]);
free(ports);
}
2014-03-12 23:55:49 +01:00
if (_device >= nDevices) {
ATA_ERROR("device ID is invalid!");
return false;
}
// 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;
2015-02-06 23:54:08 +01:00
if (_mode == airtaudio::mode_input) flag = JackPortIsOutput;
ports = jack_get_ports(client, deviceName.c_str(), nullptr, flag);
if (ports) {
2014-03-12 23:55:49 +01:00
while (ports[ nChannels ]) {
nChannels++;
}
free(ports);
}
// Compare the jack ports for specified client to the requested number of channels.
2014-03-12 23:55:49 +01:00
if (nChannels < (_channels + _firstChannel)) {
ATA_ERROR("requested number of channels (" << _channels << ") + offset (" << _firstChannel << ") not found for specified device (" << _device << ":" << deviceName << ").");
return false;
}
// Check the jack server sample rate.
uint32_t jackRate = jack_get_sample_rate(client);
2014-03-12 23:55:49 +01:00
if (_sampleRate != jackRate) {
jack_client_close(client);
ATA_ERROR("the requested sample rate (" << _sampleRate << ") is different than the JACK server rate (" << jackRate << ").");
return false;
}
2015-02-09 21:44:32 +01:00
m_sampleRate = jackRate;
// Get the latency of the JACK port.
ports = jack_get_ports(client, deviceName.c_str(), nullptr, flag);
2014-03-12 23:55:49 +01:00
if (ports[ _firstChannel ]) {
// Added by Ge Wang
2015-02-06 23:54:08 +01:00
jack_latency_callback_mode_t cbmode = (_mode == airtaudio::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
2014-03-12 23:55:49 +01:00
jack_port_get_latency_range(jack_port_by_name(client, ports[_firstChannel]), cbmode, &latrange);
// be optimistic, use the min!
2015-02-09 21:44:32 +01:00
m_latency[modeToIdTable(_mode)] = latrange.min;
//m_latency[modeToIdTable(_mode)] = jack_port_get_latency(jack_port_by_name(client, ports[ _firstChannel ]));
}
free(ports);
// The jack server always uses 32-bit floating-point data.
2015-02-09 21:44:32 +01:00
m_deviceFormat[modeToIdTable(_mode)] = audio::format_float;
m_userFormat = _format;
// Jack always uses non-interleaved buffers.
2015-02-09 21:44:32 +01:00
m_deviceInterleaved[modeToIdTable(_mode)] = false;
// Jack always provides host byte-ordered data.
2015-02-09 21:44:32 +01:00
m_doByteSwap[modeToIdTable(_mode)] = false;
// Get the buffer size. The buffer size and number of buffers
// (periods) is set when the jack server is started.
2015-02-09 21:44:32 +01:00
m_bufferSize = (int) jack_get_buffer_size(client);
*_bufferSize = m_bufferSize;
m_nDeviceChannels[modeToIdTable(_mode)] = _channels;
m_nUserChannels[modeToIdTable(_mode)] = _channels;
// Set flags for buffer conversion.
2015-02-09 21:44:32 +01:00
m_doConvertBuffer[modeToIdTable(_mode)] = false;
if (m_userFormat != m_deviceFormat[modeToIdTable(_mode)]) {
m_doConvertBuffer[modeToIdTable(_mode)] = true;
ATA_CRITICAL("Can not update format ==> use RIVER lib for this ...");
2014-03-12 23:55:49 +01:00
}
2015-02-09 21:44:32 +01:00
if ( m_deviceInterleaved[modeToIdTable(_mode)] == false
&& m_nUserChannels[modeToIdTable(_mode)] > 1) {
ATA_ERROR("Reorder channel for the interleaving properties ...");
2015-02-09 23:24:41 +01:00
m_doConvertBuffer[modeToIdTable(_mode)] = true;
2014-03-12 23:55:49 +01:00
}
// Allocate our JackHandle structure for the stream.
m_private->client = client;
m_private->deviceName[modeToIdTable(_mode)] = deviceName;
// Allocate necessary internal buffers.
uint64_t bufferBytes;
2015-02-09 21:44:32 +01:00
bufferBytes = m_nUserChannels[modeToIdTable(_mode)] * *_bufferSize * audio::getFormatBytes(m_deviceFormat[modeToIdTable(_mode)]);
ATA_VERBOSE("allocate : nbChannel=" << m_nUserChannels[modeToIdTable(_mode)] << " bufferSize=" << *_bufferSize << " format=" << m_deviceFormat[modeToIdTable(_mode)] << "=" << audio::getFormatBytes(m_deviceFormat[modeToIdTable(_mode)]));
m_userBuffer[modeToIdTable(_mode)].resize(bufferBytes, 0);
if (m_userBuffer[modeToIdTable(_mode)].size() == 0) {
ATA_ERROR("error allocating user buffer memory.");
goto error;
}
2015-02-09 21:44:32 +01:00
if (m_doConvertBuffer[modeToIdTable(_mode)]) {
bool makeBuffer = true;
2015-02-06 23:54:08 +01:00
if (_mode == airtaudio::mode_output) {
2015-02-09 21:44:32 +01:00
bufferBytes = m_nDeviceChannels[0] * audio::getFormatBytes(m_deviceFormat[0]);
2015-02-06 23:54:08 +01:00
} else { // _mode == airtaudio::mode_input
2015-02-09 21:44:32 +01:00
bufferBytes = m_nDeviceChannels[1] * audio::getFormatBytes(m_deviceFormat[1]);
if (m_mode == airtaudio::mode_output && m_deviceBuffer) {
uint64_t bytesOut = m_nDeviceChannels[0] * audio::getFormatBytes(m_deviceFormat[0]);
2014-03-12 23:55:49 +01:00
if (bufferBytes < bytesOut) {
makeBuffer = false;
}
}
}
if (makeBuffer) {
2014-03-12 23:55:49 +01:00
bufferBytes *= *_bufferSize;
2015-02-09 21:44:32 +01:00
if (m_deviceBuffer) free(m_deviceBuffer);
m_deviceBuffer = (char *) calloc(bufferBytes, 1);
if (m_deviceBuffer == nullptr) {
ATA_ERROR("error allocating device buffer memory.");
goto error;
}
}
}
// Allocate memory for the Jack ports (channels) identifiers.
m_private->ports[modeToIdTable(_mode)] = (jack_port_t **) malloc (sizeof (jack_port_t *) * _channels);
if (m_private->ports[modeToIdTable(_mode)] == nullptr) {
ATA_ERROR("error allocating port memory.");
goto error;
}
2015-02-09 21:44:32 +01:00
m_device[modeToIdTable(_mode)] = _device;
m_channelOffset[modeToIdTable(_mode)] = _firstChannel;
m_state = airtaudio::state_stopped;
if ( m_mode == airtaudio::mode_output
2015-02-06 23:54:08 +01:00
&& _mode == airtaudio::mode_input) {
// We had already set up the stream for output.
2015-02-09 21:44:32 +01:00
m_mode = airtaudio::mode_duplex;
2014-03-12 23:55:49 +01:00
} else {
2015-02-09 21:44:32 +01:00
m_mode = _mode;
jack_set_process_callback(m_private->client, &airtaudio::api::Jack::jackCallbackHandler, this);
jack_set_xrun_callback(m_private->client, &airtaudio::api::Jack::jackXrun, this);
jack_on_shutdown(m_private->client, &airtaudio::api::Jack::jackShutdown, this);
}
// Register our ports.
char label[64];
2015-02-06 23:54:08 +01:00
if (_mode == airtaudio::mode_output) {
2015-02-09 21:44:32 +01:00
for (uint32_t i=0; i<m_nUserChannels[0]; i++) {
snprintf(label, 64, "outport %d", i);
m_private->ports[0][i] = jack_port_register(m_private->client,
(const char *)label,
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0);
}
} else {
2015-02-09 21:44:32 +01:00
for (uint32_t i=0; i<m_nUserChannels[1]; i++) {
snprintf(label, 64, "inport %d", i);
m_private->ports[1][i] = jack_port_register(m_private->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.
2015-02-09 21:44:32 +01:00
if (m_doConvertBuffer[modeToIdTable(_mode)]) {
2014-03-12 23:55:49 +01:00
setConvertInfo(_mode, 0);
}
return true;
error:
jack_client_close(m_private->client);
if (m_private->ports[0] != nullptr) {
free(m_private->ports[0]);
m_private->ports[0] = nullptr;
}
if (m_private->ports[1] != nullptr) {
free(m_private->ports[1]);
m_private->ports[1] = nullptr;
}
for (int32_t iii=0; iii<2; ++iii) {
2015-02-09 21:44:32 +01:00
m_userBuffer[iii].clear();
}
2015-02-09 21:44:32 +01:00
if (m_deviceBuffer) {
free(m_deviceBuffer);
m_deviceBuffer = nullptr;
}
return false;
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Jack::closeStream() {
2015-02-09 21:44:32 +01:00
if (m_state == airtaudio::state_closed) {
ATA_ERROR("no open stream to close!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_warning;
}
if (m_private != nullptr) {
2015-02-09 21:44:32 +01:00
if (m_state == airtaudio::state_running) {
jack_deactivate(m_private->client);
2014-03-12 23:55:49 +01:00
}
jack_client_close(m_private->client);
}
if (m_private->ports[0] != nullptr) {
free(m_private->ports[0]);
m_private->ports[0] = nullptr;
}
if (m_private->ports[1] != nullptr) {
free(m_private->ports[1]);
m_private->ports[1] = nullptr;
}
for (int32_t i=0; i<2; i++) {
2015-02-09 21:44:32 +01:00
m_userBuffer[i].clear();
}
2015-02-09 21:44:32 +01:00
if (m_deviceBuffer) {
free(m_deviceBuffer);
m_deviceBuffer = nullptr;
}
2015-02-09 21:44:32 +01:00
m_mode = airtaudio::mode_unknow;
m_state = airtaudio::state_closed;
2015-02-06 23:54:08 +01:00
return airtaudio::error_none;
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Jack::startStream() {
2015-02-10 22:38:30 +01:00
// TODO : Check return ...
airtaudio::Api::startStream();
2015-02-06 23:54:08 +01:00
if (verifyStream() != airtaudio::error_none) {
return airtaudio::error_fail;
2014-03-12 23:55:49 +01:00
}
2015-02-09 21:44:32 +01:00
if (m_state == airtaudio::state_running) {
ATA_ERROR("the stream is already running!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_warning;
}
int32_t result = jack_activate(m_private->client);
if (result) {
ATA_ERROR("unable to activate JACK client!");
goto unlock;
}
const char **ports;
// Get the list of available ports.
2015-02-09 21:44:32 +01:00
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
result = 1;
ports = jack_get_ports(m_private->client, m_private->deviceName[0].c_str(), nullptr, JackPortIsInput);
if (ports == nullptr) {
ATA_ERROR("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.
2015-02-09 21:44:32 +01:00
for (uint32_t i=0; i<m_nUserChannels[0]; i++) {
result = 1;
2015-02-09 21:44:32 +01:00
if (ports[ m_channelOffset[0] + i ])
result = jack_connect(m_private->client, jack_port_name(m_private->ports[0][i]), ports[ m_channelOffset[0] + i ]);
if (result) {
free(ports);
ATA_ERROR("error connecting output ports!");
goto unlock;
}
}
free(ports);
}
2015-02-09 21:44:32 +01:00
if ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex) {
result = 1;
ports = jack_get_ports(m_private->client, m_private->deviceName[1].c_str(), nullptr, JackPortIsOutput);
if (ports == nullptr) {
ATA_ERROR("error determining available JACK output ports!");
goto unlock;
}
2014-03-12 23:55:49 +01:00
// Now make the port connections. See note above.
2015-02-09 21:44:32 +01:00
for (uint32_t i=0; i<m_nUserChannels[1]; i++) {
result = 1;
2015-02-09 21:44:32 +01:00
if (ports[ m_channelOffset[1] + i ]) {
result = jack_connect(m_private->client, ports[ m_channelOffset[1] + i ], jack_port_name(m_private->ports[1][i]));
2014-03-12 23:55:49 +01:00
}
if (result) {
free(ports);
ATA_ERROR("error connecting input ports!");
goto unlock;
}
}
free(ports);
}
m_private->drainCounter = 0;
m_private->internalDrain = false;
2015-02-09 21:44:32 +01:00
m_state = airtaudio::state_running;
2014-03-12 23:55:49 +01:00
unlock:
if (result == 0) {
2015-02-06 23:54:08 +01:00
return airtaudio::error_none;
2014-03-12 23:55:49 +01:00
}
2015-02-06 23:54:08 +01:00
return airtaudio::error_systemError;
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Jack::stopStream() {
if (verifyStream() != airtaudio::error_none) {
return airtaudio::error_fail;
2014-03-12 23:55:49 +01:00
}
2015-02-09 21:44:32 +01:00
if (m_state == airtaudio::state_stopped) {
ATA_ERROR("the stream is already stopped!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_warning;
}
2015-02-09 21:44:32 +01:00
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (m_private->drainCounter == 0) {
m_private->drainCounter = 2;
2015-02-24 22:20:11 +01:00
std11::unique_lock<std11::mutex> lck(m_mutex);
m_private->condition.wait(lck);
}
}
jack_deactivate(m_private->client);
2015-02-09 21:44:32 +01:00
m_state = airtaudio::state_stopped;
2015-02-06 23:54:08 +01:00
return airtaudio::error_none;
}
2015-02-06 23:54:08 +01:00
enum airtaudio::error airtaudio::api::Jack::abortStream() {
if (verifyStream() != airtaudio::error_none) {
return airtaudio::error_fail;
2014-03-12 23:55:49 +01:00
}
2015-02-09 21:44:32 +01:00
if (m_state == airtaudio::state_stopped) {
ATA_ERROR("the stream is already stopped!");
2015-02-06 23:54:08 +01:00
return airtaudio::error_warning;
}
m_private->drainCounter = 2;
2014-03-12 23:55:49 +01:00
return 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.
2015-02-09 21:44:32 +01:00
static void jackStopStream(void* _userData) {
2015-03-20 21:07:58 +01:00
etk::thread::setName("Jack_stopStream");
2015-02-09 21:44:32 +01:00
airtaudio::api::Jack* myClass = reinterpret_cast<airtaudio::api::Jack*>(_userData);
myClass->stopStream();
}
2014-03-12 23:55:49 +01:00
bool airtaudio::api::Jack::callbackEvent(uint64_t _nframes) {
2015-02-09 21:44:32 +01:00
if ( m_state == airtaudio::state_stopped
|| m_state == airtaudio::state_stopping) {
return true;
2014-03-12 23:55:49 +01:00
}
2015-02-09 21:44:32 +01:00
if (m_state == airtaudio::state_closed) {
ATA_ERROR("the stream is closed ... this shouldn't happen!");
return false;
}
2015-02-09 21:44:32 +01:00
if (m_bufferSize != _nframes) {
ATA_ERROR("the JACK buffer size has changed ... cannot process!");
return false;
}
// Check if we were draining the stream and signal is finished.
if (m_private->drainCounter > 3) {
2015-02-09 21:44:32 +01:00
m_state = airtaudio::state_stopping;
if (m_private->internalDrain == true) {
2015-02-24 22:20:11 +01:00
new std11::thread(jackStopStream, this);
} else {
m_private->condition.notify_one();
}
return true;
}
// Invoke user callback first, to get fresh output data.
if (m_private->drainCounter == 0) {
2015-02-24 22:20:11 +01:00
std11::chrono::time_point<std11::chrono::system_clock> streamTime = getStreamTime();
std::vector<enum airtaudio::status> status;
if (m_mode != airtaudio::mode_input && m_private->xrun[0] == true) {
status.push_back(airtaudio::status_underflow);
m_private->xrun[0] = false;
}
if (m_mode != airtaudio::mode_output && m_private->xrun[1] == true) {
status.push_back(airtaudio::status_overflow);
m_private->xrun[1] = false;
}
int32_t cbReturnValue = m_callback(&m_userBuffer[1][0],
streamTime,
&m_userBuffer[0][0],
streamTime,
m_bufferSize,
status);
if (cbReturnValue == 2) {
2015-02-09 21:44:32 +01:00
m_state = airtaudio::state_stopping;
m_private->drainCounter = 2;
2015-02-24 22:20:11 +01:00
new std11::thread(jackStopStream, this);
return true;
}
else if (cbReturnValue == 1) {
m_private->drainCounter = 1;
m_private->internalDrain = true;
}
}
jack_default_audio_sample_t *jackbuffer;
2014-03-12 23:55:49 +01:00
uint64_t bufferBytes = _nframes * sizeof(jack_default_audio_sample_t);
2015-02-09 21:44:32 +01:00
if ( m_mode == airtaudio::mode_output
|| m_mode == airtaudio::mode_duplex) {
if (m_private->drainCounter > 1) { // write zeros to the output stream
2015-02-09 21:44:32 +01:00
for (uint32_t i=0; i<m_nDeviceChannels[0]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[0][i], (jack_nframes_t) _nframes);
memset(jackbuffer, 0, bufferBytes);
}
2015-02-09 21:44:32 +01:00
} else if (m_doConvertBuffer[0]) {
convertBuffer(m_deviceBuffer, &m_userBuffer[0][0], m_convertInfo[0]);
for (uint32_t i=0; i<m_nDeviceChannels[0]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[0][i], (jack_nframes_t) _nframes);
2015-02-09 21:44:32 +01:00
memcpy(jackbuffer, &m_deviceBuffer[i*bufferBytes], bufferBytes);
}
2014-03-12 23:55:49 +01:00
} else { // no buffer conversion
2015-02-09 21:44:32 +01:00
for (uint32_t i=0; i<m_nUserChannels[0]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[0][i], (jack_nframes_t) _nframes);
2015-02-09 21:44:32 +01:00
memcpy(jackbuffer, &m_userBuffer[0][i*bufferBytes], bufferBytes);
}
}
if (m_private->drainCounter) {
m_private->drainCounter++;
goto unlock;
}
}
2015-02-09 21:44:32 +01:00
if ( m_mode == airtaudio::mode_input
|| m_mode == airtaudio::mode_duplex) {
if (m_doConvertBuffer[1]) {
for (uint32_t i=0; i<m_nDeviceChannels[1]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[1][i], (jack_nframes_t) _nframes);
2015-02-09 21:44:32 +01:00
memcpy(&m_deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes);
}
2015-02-09 21:44:32 +01:00
convertBuffer(&m_userBuffer[1][0], m_deviceBuffer, m_convertInfo[1]);
} else {
// no buffer conversion
2015-02-09 21:44:32 +01:00
for (uint32_t i=0; i<m_nUserChannels[1]; i++) {
jackbuffer = (jack_default_audio_sample_t *) jack_port_get_buffer(m_private->ports[1][i], (jack_nframes_t) _nframes);
2015-02-09 21:44:32 +01:00
memcpy(&m_userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes);
}
}
}
unlock:
airtaudio::Api::tickStreamTime();
return true;
}
2014-03-12 23:55:49 +01:00
#endif
2014-03-12 23:55:49 +01:00