Commit a733c28d authored by Fabian Schlieper's avatar Fabian Schlieper
Browse files

added jack interface

parent ff8adbcf
......@@ -10,6 +10,7 @@ include( VistaCommon )
vista_use_package( ITABase REQUIRED FIND_DEPENDENCIES )
vista_use_package( ASIO QUIET )
vista_use_package( Portaudio QUIET )
vista_use_package( JACK QUIET )
if( NOT DEFINED ITA_VISTA_BUILD_STATIC )
set( ITA_VISTA_BUILD_STATIC OFF CACHE BOOL "Build against static ViSTA libraries" )
......@@ -27,6 +28,10 @@ if( NOT DEFINED ITA_DATA_SOURCE_WITH_INTEGRATED_PORTAUDIO )
set( ITA_DATA_SOURCE_WITH_INTEGRATED_PORTAUDIO ${VPORTAUDIO_FOUND} CACHE BOOL "Build with Portaudio support" )
endif( NOT DEFINED ITA_DATA_SOURCE_WITH_INTEGRATED_PORTAUDIO )
if( NOT DEFINED ITA_DATA_SOURCE_WITH_INTEGRATED_JACK )
set( ITA_DATA_SOURCE_WITH_INTEGRATED_JACK ${VJACK_FOUND} CACHE BOOL "Build with JACK2 support" )
endif( NOT DEFINED ITA_DATA_SOURCE_WITH_INTEGRATED_JACK )
# includes
include_directories( "include" )
......@@ -85,11 +90,17 @@ if( VASIO_FOUND AND ITA_DATA_SOURCE_WITH_INTEGRATED_ASIO )
list( APPEND ITADataSourcesSources "src/ITAAsioInterface.cpp" )
add_definitions( -DIEEE754_64FLOAT=1 )
endif( VASIO_FOUND AND ITA_DATA_SOURCE_WITH_INTEGRATED_ASIO )
if( VPORTAUDIO_FOUND AND ITA_DATA_SOURCE_WITH_INTEGRATED_PORTAUDIO )
list( APPEND ITADataSourcesHeader "include/ITAPortaudioInterface.h" )
list( APPEND ITADataSourcesSources "src/ITAPortaudioInterface.cpp" )
endif( VPORTAUDIO_FOUND AND ITA_DATA_SOURCE_WITH_INTEGRATED_PORTAUDIO )
if( VJACK_FOUND AND ITA_DATA_SOURCE_WITH_INTEGRATED_JACK )
list( APPEND ITADataSourcesHeader "include/ITAJACKInterface.h" )
list( APPEND ITADataSourcesSources "src/ITAJACKInterface.cpp" )
endif( VJACK_FOUND AND ITA_DATA_SOURCE_WITH_INTEGRATED_JACK )
# compiler settings
if( ITA_VISTA_BUILD_STATIC )
......
/*
* ----------------------------------------------------------------
*
* ITA core libs
* (c) Copyright Institute of Technical Acoustics (ITA)
* RWTH Aachen University, Germany, 2015-2016
*
* ----------------------------------------------------------------
* ____ __________ _______
* // / //__ ___/ // _ |
* // / // / // /_| |
* // / // / // ___ |
* //__/ //__/ //__/ |__|
*
* ----------------------------------------------------------------
*
*/
// $Id: ITAJACKInterface.h $
#pragma once
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>
#include <queue>
#include <jack/jack.h>
#ifndef JACK_MAX_CHANNELS
#define JACK_MAX_CHANNELS 16
#endif
#include <functional>
// Forward declaration of ITADatasource
class ITADatasource;
// Forward declarations of callbacks
int ITAJackXRUN(void *arg);
void ITAJackMsg(const char *msg);
void ITAJackShutdown (void *arg);
void ITAJackPortConnected(jack_port_id_t a, jack_port_id_t b, int connect, void* arg);
void ITAJackPortRegistered(jack_port_id_t port_id, int reg, void *arg);
/*! ITAJACKInterface
*
*
*
* see also \ITADatasources
*
*/
class ITAJACKInterface {
public:
//! ITAPortaudio error code table
typedef enum ITA_JACK_ERRORCODE {
//! Portaudio/ITAPortaudio no error
ITA_JACK_NO_ERROR=0,
ITA_JACK_ERROR=1,
ITA_JACK_IS_OPEN,
ITA_JACK_IS_STARTED,
ITA_JACK_OPEN_FAILED,
ITA_JACK_NOT_INITIALIZED,
ITA_JACK_UNMATCHED_CHANNELS,
ITA_JACK_NO_PLAYBACK_DATASOURCE,
ITA_JACK_NO_RECORD_DATASOURCE,
ITA_JACK_IS_NOT_OPEN,
ITA_JACK_INTERNAL_ERROR,
ITA_JACK_STREAM_IS_STOPPED,
ITA_JACK_UNMATCHED_BUFFER_SIZE,
ITA_JACK_UNMATCHED_SAMPLE_RATE
};
//! Constructor with sample rate and buffer size
/**
* Set up internal variables of ITAPortaudio. No exception will be
* thrown here.
* \note Next do initialization
*
* \see #Initialize #Initialize(const int iDriver)
*/
ITAJACKInterface();
//! Destructor
~ITAJACKInterface();
//! Initialize Portaudio using default hardware and default host/driver
/**
* Initializes Portaudio with the current driver. If no driver has been set,
* the default output device will be used, while the input device will be
* deactivated (playback mode on, recording mode off).
*
* \return Will return error code if Portaudio could not be initialized with the current configuration, ITA_JACK_NO_ERROR otherwise
*/
ITA_JACK_ERRORCODE Initialize();
//! Initialize JACK
ITA_JACK_ERRORCODE Initialize(const std::string& clientName);
//! Use Portaudio with specific input device
ITA_JACK_ERRORCODE SetOutputDevice( int iOutputDevice );
//! Returns true if playback is enabled, false otherwise
bool IsPlaybackEnabled() const;
//! Set playback enabled/disabled
void SetPlaybackEnabled( bool bEnabled);
//! Returns true if record is enabled, false otherwise
bool IsRecordEnabled() const;
//! Set record enabled/disabled
void SetRecordEnabled( bool bEnabled);
//! Finalize Portaudio
/**
* This also deletes the record datasource.
*/
ITA_JACK_ERRORCODE Finalize();
//! Opens a Portaudio stream
ITA_JACK_ERRORCODE Open();
//! Closes the Portaudio stream
ITA_JACK_ERRORCODE Close();
//! Start Portaudio streaming
ITA_JACK_ERRORCODE Start();
//! Stop Portaudio streaming
ITA_JACK_ERRORCODE Stop();
//! Returns the number of drivers found by Portaudio
int GetNumDevices() const;
//! Returns the name of the driver avaiable in Portaudio
std::string GetDeviceName( int iDriverID ) const;
//! Returns the interactive low latency capability of the driver
/**
* \param iDriverID Identifier of driver
* \return Latency in seconds, -1 if any error with the driver occurs
*/
float GetDeviceLatency() const;
ITA_JACK_ERRORCODE GetDriverSampleRate(int iDeviceID, double& dSampleRate) const;
//! Returns the name of the current devices in Portaudio
std::string GetInputDeviceName() const;
//! Returns the name of the current devices in Portaudio
std::string GetOutputDeviceName() const;
//! Get default input device index
int GetDefaultInputDevice() const;
//! Get default output device index
int GetDefaultOutputDevice() const;
//! Get current input device index
int GetInputDevice() const;
//! Get current output device index
int GetOutputDevice() const;
//! Returns the number of input and output channels
void GetNumChannels(int iDeviceID, int& iNumInputChannels, int& iNumOutputChannels) const;
//! Returns the number of input channels
/**
* \return Number of input channels (>=0) or #ITA_JACK_ERRORCODE (<0)
*/
int GetNumInputChannels(int iDeviceID) const;
//! Returns the number of output channels
/**
* \return Number of output channels (>=0) or #ITA_JACK_ERRORCODE (<0)
*/
int GetNumOutputChannels(int iDeviceID) const;
//! Returns the sample rate
double GetSampleRate() const;
//! Sets the sample rate
ITA_JACK_ERRORCODE SetSampleRate(double dSampleRate);
//! Set the playback data source
/**
* \note Enables playback, see IsPlaybackEnabled() and SetPlaybackEnabled()
*/
ITA_JACK_ERRORCODE SetPlaybackDatasource(ITADatasource* pidsDatasource);
//! Get the recording data source
/**
* This also creates the record datasource if not already present.
* \note Enables recording, see IsRecordingEnabled() and SetRecordingEnabled()
*
* \see Finalize()
*/
ITADatasource* GetRecordDatasource();
//! Returns a human readable error code string
static std::string GetErrorCodeString(ITA_JACK_ERRORCODE err);
inline static jack_client_t *GetJackClient() { return s_jackClient; }
struct ITAJackUserData {
ITADatasource* pdsPlaybackDatasource; //!< ITADatasource playback datasource
ITADatasource* pdsRecordDatasource; //!< ITADatasource record datasource
bool bPlayback; //!< Playback enabled
bool bRecord; //!< Record enabled
jack_port_t *input_ports[JACK_MAX_CHANNELS];
jack_port_t *output_ports[JACK_MAX_CHANNELS];
uint64_t num_xruns;
uint64_t num_samples;
ITAJackUserData()
{
pdsPlaybackDatasource = NULL;
pdsRecordDatasource = NULL;
bPlayback = false;
bRecord = false;
memset(input_ports, 0, sizeof(input_ports));
memset(output_ports, 0, sizeof(output_ports));
num_xruns = 0;
num_samples = 0;
}
};
private:
static jack_client_t *s_jackClient;
jack_client_t *m_jackClient;
int m_iBufferSize;
double m_dSampleRate;
ITAJackUserData m_oUserData; //!< ITAPortaudioDatasource user data
bool m_bInitialized; //!< Portaudio initialization status
bool m_bOpen; //!< Portaudio open status
bool m_bStreaming; //!< Portaudio streaming status
bool m_bRecord; //!< Portaudio recording mode
bool m_bPlayback; //!< Portaudio playback mode
int m_iNumInputChannels; //!< Number of input channels
int m_iNumOutputChannels; //!< Number of output channels
ITA_JACK_ERRORCODE m_iError; //!< Last error
bool connectJackPorts(bool connect);
};
#include "ITAJACKInterface.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <math.h>
#include <signal.h>
#include <ITADatasource.h>
#include <ITADatasourceRealization.h>
#include <ITAStreamInfo.h>
static int ITAJackProcess (jack_nframes_t nframes, void *arg);
jack_client_t *ITAJACKInterface::s_jackClient = NULL;
static void signal_handler(int sig)
{
jack_client_t *client = ITAJACKInterface::GetJackClient();
if(client != NULL) {
fprintf(stderr, "JACKInterface: Closing client ...\n");
jack_client_close(client);
}
fprintf(stderr, "JACKInterface: signal received, exiting ...\n");
exit(0);
}
class ITAJackSource : public ITADatasourceRealization {
public:
ITAJackSource(int iChannels, double dSamplerate, int iBufferSize)
: ITADatasourceRealization((unsigned int) iChannels, dSamplerate, (unsigned int) iBufferSize, 16) {}
};
ITAJACKInterface::ITAJACKInterface()
{
m_jackClient = NULL;
m_iNumInputChannels = -1;
m_iNumOutputChannels = -1;
m_bInitialized = false;
m_bOpen = false;
m_bStreaming = false;
m_bRecord = false;
m_bPlayback = false;
m_iError = ITA_JACK_NO_ERROR;
}
ITAJACKInterface::~ITAJACKInterface() { }
ITAJACKInterface::ITA_JACK_ERRORCODE ITAJACKInterface::Initialize(const std::string& clientName) {
if (m_bInitialized)
return ITA_JACK_NO_ERROR; // allow multiple Init() calls
if (m_bOpen)
return ITA_JACK_IS_OPEN;
if (m_bStreaming)
return ITA_JACK_IS_STARTED;
int i;
jack_options_t options = JackNullOption; // JackNoStartServer; //
jack_status_t status;
jack_set_error_function(ITAJackMsg);
jack_set_info_function(ITAJackMsg);
m_jackClient = jack_client_open (clientName.c_str(), options, &status);
if (m_jackClient == NULL) {
fprintf (stderr, "jack_client_open() failed, "
"status = 0x%2.0x\n", status);
if (status & JackServerFailed) {
fprintf (stderr, "Unable to connect to JACK server\n");
}
return ITA_JACK_OPEN_FAILED;
}
s_jackClient = m_jackClient;
if (status & JackServerStarted) {
fprintf (stderr, "JACK server started\n");
}
if (status & JackNameNotUnique) {
auto clientName = std::string(jack_get_client_name(m_jackClient));
fprintf (stderr, "unique name `%s' assigned\n", clientName.c_str());
}
jack_set_process_callback (m_jackClient, ITAJackProcess, &m_oUserData);
jack_set_xrun_callback(m_jackClient, ITAJackXRUN, &m_oUserData);
jack_set_port_registration_callback(m_jackClient, ITAJackPortRegistered, &m_oUserData);
jack_on_shutdown (m_jackClient, ITAJackShutdown, 0);
jack_set_port_connect_callback(m_jackClient, ITAJackPortConnected, &m_oUserData);
m_iBufferSize = jack_get_buffer_size(m_jackClient);
m_dSampleRate = jack_get_sample_rate(m_jackClient);
// get physical ports
const char** out_ports = jack_get_ports(m_jackClient, NULL, NULL, JackPortIsPhysical|JackPortIsOutput);
const char** in_ports = jack_get_ports(m_jackClient, NULL, NULL, JackPortIsPhysical|JackPortIsInput);
m_iNumOutputChannels = 0;
m_iNumInputChannels = 0;
while (out_ports[m_iNumOutputChannels]) m_iNumOutputChannels++;
while (in_ports[m_iNumInputChannels++]) m_iNumInputChannels++;
jack_free(out_ports);
jack_free(in_ports);
char port_name[12];
for(i = 0; i < m_iNumOutputChannels; i++) {
sprintf(port_name, "out%00d", i);
m_oUserData.output_ports[i] = jack_port_register (m_jackClient, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
if (m_oUserData.output_ports[i] == NULL) {
fprintf(stderr, "no more JACK ports available\n");
return ITA_JACK_ERROR;
}
}
for(i = 0; i < m_iNumInputChannels; i++)
{
sprintf(port_name, "in%00d", i);
m_oUserData.input_ports[i] = jack_port_register (m_jackClient, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
if (m_oUserData.input_ports[i] == NULL) {
fprintf(stderr, "no more JACK ports available\n");
return ITA_JACK_ERROR;
}
}
//jack_set_sample_rate(m_jackClient, (jack_nframes_t)m_dSampleRate);
/* install a signal handler to properly quits jack client */
#ifdef WIN32
signal(SIGINT, signal_handler);
signal(SIGABRT, signal_handler);
signal(SIGTERM, signal_handler);
#else
signal(SIGQUIT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGHUP, signal_handler);
signal(SIGINT, signal_handler);
#endif
m_bInitialized = true;
return ITA_JACK_NO_ERROR;
}
bool ITAJACKInterface::IsPlaybackEnabled() const {
return m_bPlayback;
}
void ITAJACKInterface::SetPlaybackEnabled(const bool bEnabled) {
m_bPlayback = bEnabled;
}
bool ITAJACKInterface::IsRecordEnabled() const {
return m_bRecord;
}
void ITAJACKInterface::SetRecordEnabled(const bool bEnabled) {
m_bRecord = bEnabled;
}
ITAJACKInterface::ITA_JACK_ERRORCODE ITAJACKInterface::Finalize() {
if (!m_bInitialized)
return ITA_JACK_NOT_INITIALIZED;
if (m_bStreaming)
return ITA_JACK_IS_STARTED;
m_iError = ITA_JACK_NO_ERROR;
for(int i = 0; i < m_iNumInputChannels; i++)
jack_port_unregister(m_jackClient, m_oUserData.input_ports[i]);
for(int i = 0; i < m_iNumOutputChannels; i++)
jack_port_unregister(m_jackClient, m_oUserData.output_ports[i]);
jack_client_close(m_jackClient);
if (m_iError == ITA_JACK_NO_ERROR)
m_bInitialized = false;
if (m_oUserData.pdsRecordDatasource != NULL) {
delete m_oUserData.pdsRecordDatasource;
m_oUserData.pdsRecordDatasource = NULL;
}
return m_iError;
}
// Actually does nothing
ITAJACKInterface::ITA_JACK_ERRORCODE ITAJACKInterface::Open() {
if (!m_bInitialized) return ITA_JACK_NOT_INITIALIZED;
if (m_bOpen) return ITA_JACK_IS_OPEN;
if (m_oUserData.pdsPlaybackDatasource == NULL && m_bPlayback) return ITA_JACK_NO_PLAYBACK_DATASOURCE;
if (m_oUserData.pdsRecordDatasource == NULL && m_bRecord) return ITA_JACK_NO_RECORD_DATASOURCE;
if (m_bRecord) {
if ((int) m_oUserData.pdsRecordDatasource->GetNumberOfChannels() > m_iNumInputChannels)
return ITA_JACK_UNMATCHED_CHANNELS;
m_oUserData.bRecord = true;
}
if (m_bPlayback) {
if ((int) m_oUserData.pdsPlaybackDatasource->GetNumberOfChannels() > m_iNumOutputChannels)
return ITA_JACK_UNMATCHED_CHANNELS;
m_oUserData.bPlayback = true;
}
m_bOpen = true;
return m_iError;
}
// Actually does nothing
ITAJACKInterface::ITA_JACK_ERRORCODE ITAJACKInterface::Close() {
if (!m_bInitialized)
return ITA_JACK_NOT_INITIALIZED;
if (!m_bOpen)
return ITA_JACK_IS_NOT_OPEN;
m_bOpen = false;
return ITA_JACK_NO_ERROR;
}
bool ITAJACKInterface::connectJackPorts(bool connect)
{
char portname[64]; // enough to hold all numbers up to 64-bits
int i;
for (i=0; i < m_iNumInputChannels; i++) {
sprintf(portname, "system:capture_%d", i+1);
if (connect ? jack_connect(m_jackClient, portname, jack_port_name(m_oUserData.input_ports[i]))
: jack_disconnect(m_jackClient, portname, jack_port_name(m_oUserData.input_ports[i]))) {
fprintf(stderr,"Could not %s input ports %s and %s!\n", connect ? "connect" : "disconnect", portname, jack_port_name(m_oUserData.input_ports[i]));
//return false;
}
}
for (i=0; i < m_iNumOutputChannels; i++) {
sprintf(portname, "system:playback_%d", i+1);
if (connect ? jack_connect(m_jackClient, jack_port_name(m_oUserData.output_ports[i]), portname)
: jack_disconnect(m_jackClient, jack_port_name(m_oUserData.output_ports[i]), portname)) {
fprintf(stderr,"Could not %s output ports %s and %s!\n", connect ? "connect" : "disconnect", jack_port_name(m_oUserData.output_ports[i]), portname);
//return false;
}
}
return true;
}
ITAJACKInterface::ITA_JACK_ERRORCODE ITAJACKInterface::Start() {
if (m_bStreaming)
return ITA_JACK_IS_STARTED;
if (!m_bInitialized)
return ITA_JACK_NOT_INITIALIZED;
if (!m_bOpen)
return ITA_JACK_IS_NOT_OPEN;
/* Tell the JACK server that we are ready to roll. Our
* process() callback will start running now. */
if (jack_activate (m_jackClient)) {
return ITA_JACK_INTERNAL_ERROR;
}
if(!connectJackPorts(true))
return ITA_JACK_UNMATCHED_CHANNELS;
m_bStreaming = true;
return ITA_JACK_NO_ERROR;
}
ITAJACKInterface::ITA_JACK_ERRORCODE ITAJACKInterface::Stop() {
if (!m_bStreaming)
return ITA_JACK_STREAM_IS_STOPPED;
if (!m_bInitialized)
return ITA_JACK_NOT_INITIALIZED;
if(!connectJackPorts(false))
return ITA_JACK_UNMATCHED_CHANNELS;
jack_deactivate(m_jackClient);
m_bStreaming = false;
return ITA_JACK_NO_ERROR;
}
int ITAJACKInterface::GetNumDevices() const {
if (!m_bInitialized)
return -1;
return 1;