mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-08 01:56:10 -05:00
5b9645bf09
git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/trunk@189 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
1715 lines
60 KiB
C
1715 lines
60 KiB
C
/*
|
|
* $Id$
|
|
* PortAudio Portable Real-Time Audio Library
|
|
* Latest Version at: http://www.portaudio.com
|
|
* JACK Implementation by Joshua Haberman
|
|
*
|
|
* Copyright (c) 2004 Stefan Westerfeld <stefan@space.twc.de>
|
|
* Copyright (c) 2004 Arve Knudsen <aknuds-1@broadpark.no>
|
|
* Copyright (c) 2002 Joshua Haberman <joshua@haberman.com>
|
|
*
|
|
* Based on the Open Source API proposed by Ross Bencina
|
|
* Copyright (c) 1999-2002 Ross Bencina, Phil Burk
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files
|
|
* (the "Software"), to deal in the Software without restriction,
|
|
* including without limitation the rights to use, copy, modify, merge,
|
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* Any person wishing to distribute modifications to the Software is
|
|
* requested to send the modifications to the original developer so that
|
|
* they can be incorporated into the canonical version.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <regex.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <errno.h> /* EBUSY */
|
|
#include <signal.h> /* sig_atomic_t */
|
|
#include <math.h>
|
|
#include <semaphore.h>
|
|
|
|
#include <jack/types.h>
|
|
#include <jack/jack.h>
|
|
|
|
#include "pa_util.h"
|
|
#include "pa_hostapi.h"
|
|
#include "pa_stream.h"
|
|
#include "pa_process.h"
|
|
#include "pa_allocation.h"
|
|
#include "pa_cpuload.h"
|
|
#include "../pablio/ringbuffer.c"
|
|
|
|
static int aErr_;
|
|
static PaError paErr_; /* For use with ENSURE_PA */
|
|
static pthread_t mainThread_;
|
|
static char *jackErr_ = NULL;
|
|
|
|
#define STRINGIZE_HELPER(expr) #expr
|
|
#define STRINGIZE(expr) STRINGIZE_HELPER(expr)
|
|
|
|
/* Check PaError */
|
|
#define ENSURE_PA(expr) \
|
|
do { \
|
|
if( (paErr_ = (expr)) < paNoError ) \
|
|
{ \
|
|
if( (paErr_) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \
|
|
{ \
|
|
assert( jackErr_ ); \
|
|
PaUtil_SetLastHostErrorInfo( paJACK, -1, jackErr_ ); \
|
|
} \
|
|
PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
|
|
result = paErr_; \
|
|
goto error; \
|
|
} \
|
|
} while( 0 )
|
|
|
|
#define UNLESS(expr, code) \
|
|
do { \
|
|
if( (expr) == 0 ) \
|
|
{ \
|
|
if( (code) == paUnanticipatedHostError && pthread_self() == mainThread_ ) \
|
|
{ \
|
|
assert( jackErr_ ); \
|
|
PaUtil_SetLastHostErrorInfo( paJACK, -1, jackErr_ ); \
|
|
} \
|
|
PaUtil_DebugPrint(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
|
|
result = (code); \
|
|
goto error; \
|
|
} \
|
|
} while( 0 )
|
|
|
|
#define ASSERT_CALL(expr, success) \
|
|
aErr_ = (expr); \
|
|
assert( aErr_ == success );
|
|
|
|
/*
|
|
* Functions that directly map to the PortAudio stream interface
|
|
*/
|
|
|
|
static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
|
|
static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
|
|
const PaStreamParameters *inputParameters,
|
|
const PaStreamParameters *outputParameters,
|
|
double sampleRate );
|
|
static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
|
|
PaStream** s,
|
|
const PaStreamParameters *inputParameters,
|
|
const PaStreamParameters *outputParameters,
|
|
double sampleRate,
|
|
unsigned long framesPerBuffer,
|
|
PaStreamFlags streamFlags,
|
|
PaStreamCallback *streamCallback,
|
|
void *userData );
|
|
static PaError CloseStream( PaStream* stream );
|
|
static PaError StartStream( PaStream *stream );
|
|
static PaError StopStream( PaStream *stream );
|
|
static PaError AbortStream( PaStream *stream );
|
|
static PaError IsStreamStopped( PaStream *s );
|
|
static PaError IsStreamActive( PaStream *stream );
|
|
/*static PaTime GetStreamInputLatency( PaStream *stream );*/
|
|
/*static PaTime GetStreamOutputLatency( PaStream *stream );*/
|
|
static PaTime GetStreamTime( PaStream *stream );
|
|
static double GetStreamCpuLoad( PaStream* stream );
|
|
|
|
|
|
/*
|
|
* Data specific to this API
|
|
*/
|
|
|
|
struct PaJackStream;
|
|
|
|
typedef struct
|
|
{
|
|
PaUtilHostApiRepresentation commonHostApiRep;
|
|
PaUtilStreamInterface callbackStreamInterface;
|
|
PaUtilStreamInterface blockingStreamInterface;
|
|
|
|
PaUtilAllocationGroup *deviceInfoMemory;
|
|
|
|
jack_client_t *jack_client;
|
|
int jack_buffer_size;
|
|
PaHostApiIndex hostApiIndex;
|
|
|
|
pthread_mutex_t mtx;
|
|
pthread_cond_t cond;
|
|
unsigned long inputBase, outputBase;
|
|
|
|
/* For dealing with the process thread */
|
|
volatile int xrun; /* Received xrun notification from JACK? */
|
|
struct PaJackStream * volatile toAdd, * volatile toRemove;
|
|
struct PaJackStream *processQueue;
|
|
volatile sig_atomic_t jackIsDown;
|
|
}
|
|
PaJackHostApiRepresentation;
|
|
|
|
/* PaJackStream - a stream data structure specifically for this implementation */
|
|
|
|
typedef struct PaJackStream
|
|
{
|
|
PaUtilStreamRepresentation streamRepresentation;
|
|
PaUtilBufferProcessor bufferProcessor;
|
|
PaUtilCpuLoadMeasurer cpuLoadMeasurer;
|
|
PaJackHostApiRepresentation *hostApi;
|
|
|
|
/* our input and output ports */
|
|
jack_port_t **local_input_ports;
|
|
jack_port_t **local_output_ports;
|
|
|
|
/* the input and output ports of the client we are connecting to */
|
|
jack_port_t **remote_input_ports;
|
|
jack_port_t **remote_output_ports;
|
|
|
|
int num_incoming_connections;
|
|
int num_outgoing_connections;
|
|
|
|
jack_client_t *jack_client;
|
|
|
|
/* The stream is running if it's still producing samples.
|
|
* The stream is active if samples it produced are still being heard.
|
|
*/
|
|
volatile sig_atomic_t is_running;
|
|
volatile sig_atomic_t is_active;
|
|
/* Used to signal processing thread that stream should start or stop, respectively */
|
|
volatile sig_atomic_t doStart, doStop, doAbort;
|
|
|
|
jack_nframes_t t0;
|
|
|
|
PaUtilAllocationGroup *stream_memory;
|
|
|
|
/* These are useful in the process callback */
|
|
|
|
int callbackResult;
|
|
int isSilenced;
|
|
int xrun;
|
|
|
|
/* These are useful for the blocking API */
|
|
|
|
int isBlockingStream;
|
|
RingBuffer inFIFO;
|
|
RingBuffer outFIFO;
|
|
volatile sig_atomic_t data_available;
|
|
sem_t data_semaphore;
|
|
int bytesPerFrame;
|
|
int samplesPerFrame;
|
|
|
|
struct PaJackStream *next;
|
|
}
|
|
PaJackStream;
|
|
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
|
|
/*
|
|
* Functions specific to this API
|
|
*/
|
|
|
|
static int JackCallback( jack_nframes_t frames, void *userData );
|
|
|
|
|
|
/*
|
|
*
|
|
* Implementation
|
|
*
|
|
*/
|
|
|
|
/* ---- blocking emulation layer ---- */
|
|
|
|
/* Allocate buffer. */
|
|
static PaError BlockingInitFIFO( RingBuffer *rbuf, long numFrames, long bytesPerFrame )
|
|
{
|
|
long numBytes = numFrames * bytesPerFrame;
|
|
char *buffer = (char *) malloc( numBytes );
|
|
if( buffer == NULL ) return paInsufficientMemory;
|
|
memset( buffer, 0, numBytes );
|
|
return (PaError) RingBuffer_Init( rbuf, numBytes, buffer );
|
|
}
|
|
|
|
/* Free buffer. */
|
|
static PaError BlockingTermFIFO( RingBuffer *rbuf )
|
|
{
|
|
if( rbuf->buffer ) free( rbuf->buffer );
|
|
rbuf->buffer = NULL;
|
|
return paNoError;
|
|
}
|
|
|
|
static int
|
|
BlockingCallback( const void *inputBuffer,
|
|
void *outputBuffer,
|
|
unsigned long framesPerBuffer,
|
|
const PaStreamCallbackTimeInfo* timeInfo,
|
|
PaStreamCallbackFlags statusFlags,
|
|
void *userData )
|
|
{
|
|
struct PaJackStream *stream = (PaJackStream *)userData;
|
|
long numBytes = stream->bytesPerFrame * framesPerBuffer;
|
|
|
|
/* This may get called with NULL inputBuffer during initial setup. */
|
|
if( inputBuffer != NULL )
|
|
{
|
|
RingBuffer_Write( &stream->inFIFO, inputBuffer, numBytes );
|
|
}
|
|
if( outputBuffer != NULL )
|
|
{
|
|
int numRead = RingBuffer_Read( &stream->outFIFO, outputBuffer, numBytes );
|
|
/* Zero out remainder of buffer if we run out of data. */
|
|
memset( (char *)outputBuffer + numRead, 0, numBytes - numRead );
|
|
}
|
|
|
|
if( !stream->data_available )
|
|
{
|
|
stream->data_available = 1;
|
|
sem_post( &stream->data_semaphore );
|
|
}
|
|
return paContinue;
|
|
}
|
|
|
|
static PaError
|
|
BlockingBegin( PaJackStream *stream, int minimum_buffer_size )
|
|
{
|
|
long doRead = 0;
|
|
long doWrite = 0;
|
|
PaError result = paNoError;
|
|
long numFrames;
|
|
|
|
doRead = stream->local_input_ports != NULL;
|
|
doWrite = stream->local_output_ports != NULL;
|
|
/* <FIXME> */
|
|
stream->samplesPerFrame = 2;
|
|
stream->bytesPerFrame = sizeof(float) * stream->samplesPerFrame;
|
|
/* </FIXME> */
|
|
numFrames = 32;
|
|
while (numFrames < minimum_buffer_size)
|
|
numFrames *= 2;
|
|
|
|
if( doRead )
|
|
{
|
|
ENSURE_PA( BlockingInitFIFO( &stream->inFIFO, numFrames, stream->bytesPerFrame ) );
|
|
}
|
|
if( doWrite )
|
|
{
|
|
long numBytes;
|
|
|
|
ENSURE_PA( BlockingInitFIFO( &stream->outFIFO, numFrames, stream->bytesPerFrame ) );
|
|
|
|
/* Make Write FIFO appear full initially. */
|
|
numBytes = RingBuffer_GetWriteAvailable( &stream->outFIFO );
|
|
RingBuffer_AdvanceWriteIndex( &stream->outFIFO, numBytes );
|
|
}
|
|
|
|
stream->data_available = 0;
|
|
sem_init( &stream->data_semaphore, 0, 0 );
|
|
|
|
error:
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
BlockingEnd( PaJackStream *stream )
|
|
{
|
|
BlockingTermFIFO( &stream->inFIFO );
|
|
BlockingTermFIFO( &stream->outFIFO );
|
|
|
|
sem_destroy( &stream->data_semaphore );
|
|
}
|
|
|
|
static PaError BlockingReadStream( PaStream* s, void *data, unsigned long numFrames )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackStream *stream = (PaJackStream *)s;
|
|
|
|
long bytesRead;
|
|
char *p = (char *) data;
|
|
long numBytes = stream->bytesPerFrame * numFrames;
|
|
while( numBytes > 0 )
|
|
{
|
|
bytesRead = RingBuffer_Read( &stream->inFIFO, p, numBytes );
|
|
numBytes -= bytesRead;
|
|
p += bytesRead;
|
|
if( numBytes > 0 )
|
|
{
|
|
/* see write for an explanation */
|
|
if( stream->data_available )
|
|
stream->data_available = 0;
|
|
else
|
|
sem_wait( &stream->data_semaphore );
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static PaError BlockingWriteStream( PaStream* s, const void *data, unsigned long numFrames )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackStream *stream = (PaJackStream *)s;
|
|
long bytesWritten;
|
|
char *p = (char *) data;
|
|
long numBytes = stream->bytesPerFrame * numFrames;
|
|
while( numBytes > 0 )
|
|
{
|
|
bytesWritten = RingBuffer_Write( &stream->outFIFO, p, numBytes );
|
|
numBytes -= bytesWritten;
|
|
p += bytesWritten;
|
|
if( numBytes > 0 )
|
|
{
|
|
/* we use the following algorithm:
|
|
* (1) write data
|
|
* (2) if some data didn't fit into the ringbuffer, set data_available to 0
|
|
* to indicate to the audio that if space becomes available, we want to know
|
|
* (3) retry to write data (because it might be that between (1) and (2)
|
|
* new space in the buffer became available)
|
|
* (4) if this failed, we are sure that the buffer is really empty and
|
|
* we will definitely receive a notification when it becomes available
|
|
* thus we can safely sleep
|
|
*
|
|
* if the algorithm bailed out in step (3) before, it leaks a count of 1
|
|
* on the semaphore; however, it doesn't matter, because if we block in (4),
|
|
* we also do it in a loop
|
|
*/
|
|
if( stream->data_available )
|
|
stream->data_available = 0;
|
|
else
|
|
sem_wait( &stream->data_semaphore );
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static signed long
|
|
BlockingGetStreamReadAvailable( PaStream* s )
|
|
{
|
|
PaJackStream *stream = (PaJackStream *)s;
|
|
|
|
int bytesFull = RingBuffer_GetReadAvailable( &stream->inFIFO );
|
|
return bytesFull / stream->bytesPerFrame;
|
|
}
|
|
|
|
static signed long
|
|
BlockingGetStreamWriteAvailable( PaStream* s )
|
|
{
|
|
PaJackStream *stream = (PaJackStream *)s;
|
|
|
|
int bytesEmpty = RingBuffer_GetWriteAvailable( &stream->outFIFO );
|
|
return bytesEmpty / stream->bytesPerFrame;
|
|
}
|
|
|
|
static PaError
|
|
BlockingWaitEmpty( PaStream *s )
|
|
{
|
|
PaJackStream *stream = (PaJackStream *)s;
|
|
|
|
while( RingBuffer_GetReadAvailable( &stream->outFIFO ) > 0 )
|
|
{
|
|
stream->data_available = 0;
|
|
sem_wait( &stream->data_semaphore );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ---- jack driver ---- */
|
|
|
|
/* BuildDeviceList():
|
|
*
|
|
* The process of determining a list of PortAudio "devices" from
|
|
* JACK's client/port system is fairly involved, so it is separated
|
|
* into its own routine.
|
|
*/
|
|
|
|
static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi )
|
|
{
|
|
/* Utility macros for the repetitive process of allocating memory */
|
|
|
|
/* ... MALLOC: allocate memory as part of the device list
|
|
* allocation group */
|
|
#define MALLOC(size) \
|
|
(PaUtil_GroupAllocateMemory( jackApi->deviceInfoMemory, (size) ))
|
|
|
|
/* JACK has no concept of a device. To JACK, there are clients
|
|
* which have an arbitrary number of ports. To make this
|
|
* intelligible to PortAudio clients, we will group each JACK client
|
|
* into a device, and make each port of that client a channel */
|
|
|
|
PaError result = paNoError;
|
|
PaUtilHostApiRepresentation *commonApi = &jackApi->commonHostApiRep;
|
|
|
|
const char **jack_ports = NULL;
|
|
char **client_names = NULL;
|
|
char *regex_pattern = alloca( jack_client_name_size() + 3 );
|
|
int port_index, client_index, i;
|
|
double globalSampleRate;
|
|
regex_t port_regex;
|
|
unsigned long numClients = 0, numPorts = 0;
|
|
char *tmp_client_name = alloca( jack_client_name_size() );
|
|
|
|
commonApi->info.defaultInputDevice = paNoDevice;
|
|
commonApi->info.defaultOutputDevice = paNoDevice;
|
|
commonApi->info.deviceCount = 0;
|
|
|
|
/* Parse the list of ports, using a regex to grab the client names */
|
|
ASSERT_CALL( regcomp( &port_regex, "^[^:]*", REG_EXTENDED ), 0 );
|
|
|
|
/* since we are rebuilding the list of devices, free all memory
|
|
* associated with the previous list */
|
|
PaUtil_FreeAllAllocations( jackApi->deviceInfoMemory );
|
|
|
|
/* We can only retrieve the list of clients indirectly, by first
|
|
* asking for a list of all ports, then parsing the port names
|
|
* according to the client_name:port_name convention (which is
|
|
* enforced by jackd)
|
|
* A: If jack_get_ports returns NULL, there's nothing for us to do */
|
|
UNLESS( (jack_ports = jack_get_ports( jackApi->jack_client, "", "", 0 )) && jack_ports[0], paNoError );
|
|
/* Find number of ports */
|
|
while( jack_ports[numPorts] )
|
|
++numPorts;
|
|
/* At least there will be one port per client :) */
|
|
UNLESS( client_names = alloca( numPorts * sizeof (char *) ), paInsufficientMemory );
|
|
|
|
/* Build a list of clients from the list of ports */
|
|
for( numClients = 0, port_index = 0; jack_ports[port_index] != NULL; port_index++ )
|
|
{
|
|
int client_seen = FALSE;
|
|
regmatch_t match_info;
|
|
const char *port = jack_ports[port_index];
|
|
|
|
/* extract the client name from the port name, using a regex
|
|
* that parses the clientname:portname syntax */
|
|
UNLESS( !regexec( &port_regex, port, 1, &match_info, 0 ), paInternalError );
|
|
assert(match_info.rm_eo - match_info.rm_so < jack_client_name_size());
|
|
memcpy( tmp_client_name, port + match_info.rm_so,
|
|
match_info.rm_eo - match_info.rm_so );
|
|
tmp_client_name[match_info.rm_eo - match_info.rm_so] = '\0';
|
|
|
|
/* do we know about this port's client yet? */
|
|
for( i = 0; i < numClients; i++ )
|
|
{
|
|
if( strcmp( tmp_client_name, client_names[i] ) == 0 )
|
|
client_seen = TRUE;
|
|
}
|
|
|
|
if (client_seen)
|
|
continue; /* A: Nothing to see here, move along */
|
|
|
|
UNLESS( client_names[numClients] = (char*)MALLOC(strlen(tmp_client_name) + 1), paInsufficientMemory );
|
|
|
|
/* The alsa_pcm client should go in spot 0. If this
|
|
* is the alsa_pcm client AND we are NOT about to put
|
|
* it in spot 0 put it in spot 0 and move whatever
|
|
* was already in spot 0 to the end. */
|
|
if( strcmp( "alsa_pcm", tmp_client_name ) == 0 && numClients > 0 )
|
|
{
|
|
/* alsa_pcm goes in spot 0 */
|
|
strcpy( client_names[ numClients ], client_names[0] );
|
|
strcpy( client_names[0], tmp_client_name );
|
|
}
|
|
else
|
|
{
|
|
/* put the new client at the end of the client list */
|
|
strcpy( client_names[ numClients ], tmp_client_name );
|
|
}
|
|
++numClients;
|
|
}
|
|
|
|
/* Now we have a list of clients, which will become the list of
|
|
* PortAudio devices. */
|
|
|
|
/* there is one global sample rate all clients must conform to */
|
|
|
|
globalSampleRate = jack_get_sample_rate( jackApi->jack_client );
|
|
UNLESS( commonApi->deviceInfos = (PaDeviceInfo**)MALLOC( sizeof(PaDeviceInfo*) *
|
|
numClients ), paInsufficientMemory );
|
|
|
|
assert( commonApi->info.deviceCount == 0 );
|
|
|
|
/* Create a PaDeviceInfo structure for every client */
|
|
for( client_index = 0; client_index < numClients; client_index++ )
|
|
{
|
|
PaDeviceInfo *curDevInfo;
|
|
const char **clientPorts = NULL;
|
|
|
|
UNLESS( curDevInfo = (PaDeviceInfo*)MALLOC( sizeof(PaDeviceInfo) ), paInsufficientMemory );
|
|
UNLESS( curDevInfo->name = (char*)MALLOC( strlen(client_names[client_index]) + 1 ), paInsufficientMemory );
|
|
strcpy( (char *)curDevInfo->name, client_names[client_index] );
|
|
|
|
curDevInfo->structVersion = 2;
|
|
curDevInfo->hostApi = jackApi->hostApiIndex;
|
|
|
|
/* JACK is very inflexible: there is one sample rate the whole
|
|
* system must run at, and all clients must speak IEEE float. */
|
|
curDevInfo->defaultSampleRate = globalSampleRate;
|
|
|
|
/* To determine how many input and output channels are available,
|
|
* we re-query jackd with more specific parameters. */
|
|
|
|
sprintf( regex_pattern, "%s:.*", client_names[client_index] );
|
|
|
|
/* ... what are your output ports (that we could input from)? */
|
|
clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern,
|
|
NULL, JackPortIsOutput);
|
|
curDevInfo->maxInputChannels = 0;
|
|
curDevInfo->defaultLowInputLatency = 0.;
|
|
curDevInfo->defaultHighInputLatency = 0.;
|
|
if( clientPorts )
|
|
{
|
|
jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] );
|
|
curDevInfo->defaultLowInputLatency = curDevInfo->defaultHighInputLatency =
|
|
jack_port_get_latency( p ) / globalSampleRate;
|
|
|
|
for( i = 0; clientPorts[i] != NULL; i++)
|
|
{
|
|
/* The number of ports returned is the number of output channels.
|
|
* We don't care what they are, we just care how many */
|
|
curDevInfo->maxInputChannels++;
|
|
}
|
|
free(clientPorts);
|
|
}
|
|
|
|
/* ... what are your input ports (that we could output to)? */
|
|
clientPorts = jack_get_ports( jackApi->jack_client, regex_pattern,
|
|
NULL, JackPortIsInput);
|
|
curDevInfo->maxOutputChannels = 0;
|
|
curDevInfo->defaultLowOutputLatency = 0.;
|
|
curDevInfo->defaultHighOutputLatency = 0.;
|
|
if( clientPorts )
|
|
{
|
|
jack_port_t *p = jack_port_by_name( jackApi->jack_client, clientPorts[0] );
|
|
curDevInfo->defaultLowOutputLatency = curDevInfo->defaultHighOutputLatency =
|
|
jack_port_get_latency( p ) / globalSampleRate;
|
|
|
|
for( i = 0; clientPorts[i] != NULL; i++)
|
|
{
|
|
/* The number of ports returned is the number of input channels.
|
|
* We don't care what they are, we just care how many */
|
|
curDevInfo->maxOutputChannels++;
|
|
}
|
|
free(clientPorts);
|
|
}
|
|
|
|
/* Add this client to the list of devices */
|
|
commonApi->deviceInfos[client_index] = curDevInfo;
|
|
++commonApi->info.deviceCount;
|
|
if( commonApi->info.defaultInputDevice == paNoDevice && curDevInfo->maxInputChannels > 0 )
|
|
commonApi->info.defaultInputDevice = client_index;
|
|
if( commonApi->info.defaultOutputDevice == paNoDevice && curDevInfo->maxOutputChannels > 0 )
|
|
commonApi->info.defaultOutputDevice = client_index;
|
|
}
|
|
|
|
error:
|
|
regfree( &port_regex );
|
|
free( jack_ports );
|
|
return result;
|
|
}
|
|
#undef MALLOC
|
|
|
|
static void UpdateSampleRate( PaJackStream *stream, double sampleRate )
|
|
{
|
|
/* XXX: Maybe not the cleanest way of going about this? */
|
|
stream->cpuLoadMeasurer.samplingPeriod = stream->bufferProcessor.samplePeriod = 1. / sampleRate;
|
|
stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
|
|
}
|
|
|
|
static void JackErrorCallback( const char *msg )
|
|
{
|
|
if( pthread_self() == mainThread_ )
|
|
{
|
|
assert( msg );
|
|
free( jackErr_ );
|
|
jackErr_ = malloc( strlen( msg ) );
|
|
sprintf( jackErr_, msg );
|
|
}
|
|
}
|
|
|
|
static void JackOnShutdown( void *arg )
|
|
{
|
|
PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg;
|
|
PaJackStream *stream = jackApi->processQueue;
|
|
|
|
PA_DEBUG(( "%s: JACK server is shutting down\n", __FUNCTION__ ));
|
|
for( ; stream; stream = stream->next )
|
|
{
|
|
stream->is_active = 0;
|
|
}
|
|
|
|
/* Make sure that the main thread doesn't get stuck waiting on the condition */
|
|
ASSERT_CALL( pthread_mutex_lock( &jackApi->mtx ), 0 );
|
|
jackApi->jackIsDown = 1;
|
|
ASSERT_CALL( pthread_cond_signal( &jackApi->cond ), 0 );
|
|
ASSERT_CALL( pthread_mutex_unlock( &jackApi->mtx ), 0 );
|
|
|
|
}
|
|
|
|
static int JackSrCb( jack_nframes_t nframes, void *arg )
|
|
{
|
|
PaJackHostApiRepresentation *jackApi = (PaJackHostApiRepresentation *)arg;
|
|
double sampleRate = (double)nframes;
|
|
PaJackStream *stream = jackApi->processQueue;
|
|
|
|
/* Update all streams in process queue */
|
|
PA_DEBUG(( "%s: Acting on change in JACK samplerate: %f\n", __FUNCTION__, sampleRate ));
|
|
for( ; stream; stream = stream->next )
|
|
{
|
|
if( stream->streamRepresentation.streamInfo.sampleRate != sampleRate )
|
|
{
|
|
PA_DEBUG(( "%s: Updating samplerate\n", __FUNCTION__ ));
|
|
UpdateSampleRate( stream, sampleRate );
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int JackXRunCb(void *arg) {
|
|
PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)arg;
|
|
assert( hostApi );
|
|
hostApi->xrun = TRUE;
|
|
PA_DEBUG(( "%s: JACK signalled xrun\n", __FUNCTION__ ));
|
|
return 0;
|
|
}
|
|
|
|
PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi,
|
|
PaHostApiIndex hostApiIndex )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackHostApiRepresentation *jackHostApi;
|
|
int activated = 0;
|
|
char *clientName;
|
|
int written;
|
|
*hostApi = NULL; /* Initialize to NULL */
|
|
|
|
UNLESS( jackHostApi = (PaJackHostApiRepresentation*)
|
|
PaUtil_AllocateMemory( sizeof(PaJackHostApiRepresentation) ), paInsufficientMemory );
|
|
jackHostApi->deviceInfoMemory = NULL;
|
|
|
|
mainThread_ = pthread_self();
|
|
ASSERT_CALL( pthread_mutex_init( &jackHostApi->mtx, NULL ), 0 );
|
|
ASSERT_CALL( pthread_cond_init( &jackHostApi->cond, NULL ), 0 );
|
|
|
|
/* Try to become a client of the JACK server. If we cannot do
|
|
* this, then this API cannot be used. */
|
|
|
|
clientName = alloca( jack_client_name_size() );
|
|
written = snprintf( clientName, jack_client_name_size(), "PortAudio-%d", getpid() );
|
|
assert( written < jack_client_name_size() );
|
|
jackHostApi->jack_client = jack_client_new( clientName );
|
|
if( jackHostApi->jack_client == NULL )
|
|
{
|
|
/* the V19 development docs say that if an implementation
|
|
* detects that it cannot be used, it should return a NULL
|
|
* interface and paNoError */
|
|
result = paNoError;
|
|
goto error;
|
|
}
|
|
|
|
UNLESS( jackHostApi->deviceInfoMemory = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
|
|
jackHostApi->hostApiIndex = hostApiIndex;
|
|
|
|
*hostApi = &jackHostApi->commonHostApiRep;
|
|
(*hostApi)->info.structVersion = 1;
|
|
(*hostApi)->info.type = paJACK;
|
|
(*hostApi)->info.name = "JACK Audio Connection Kit";
|
|
|
|
/* Build a device list by querying the JACK server */
|
|
|
|
ENSURE_PA( BuildDeviceList( jackHostApi ) );
|
|
|
|
/* Register functions */
|
|
|
|
(*hostApi)->Terminate = Terminate;
|
|
(*hostApi)->OpenStream = OpenStream;
|
|
(*hostApi)->IsFormatSupported = IsFormatSupported;
|
|
|
|
PaUtil_InitializeStreamInterface( &jackHostApi->callbackStreamInterface,
|
|
CloseStream, StartStream,
|
|
StopStream, AbortStream,
|
|
IsStreamStopped, IsStreamActive,
|
|
GetStreamTime, GetStreamCpuLoad,
|
|
PaUtil_DummyRead, PaUtil_DummyWrite,
|
|
PaUtil_DummyGetReadAvailable,
|
|
PaUtil_DummyGetWriteAvailable );
|
|
|
|
PaUtil_InitializeStreamInterface( &jackHostApi->blockingStreamInterface, CloseStream, StartStream,
|
|
StopStream, AbortStream, IsStreamStopped, IsStreamActive,
|
|
GetStreamTime, PaUtil_DummyGetCpuLoad,
|
|
BlockingReadStream, BlockingWriteStream,
|
|
BlockingGetStreamReadAvailable, BlockingGetStreamWriteAvailable );
|
|
|
|
jackHostApi->inputBase = jackHostApi->outputBase = 0;
|
|
jackHostApi->xrun = 0;
|
|
jackHostApi->toAdd = jackHostApi->toRemove = NULL;
|
|
jackHostApi->processQueue = NULL;
|
|
jackHostApi->jackIsDown = 0;
|
|
|
|
jack_on_shutdown( jackHostApi->jack_client, JackOnShutdown, jackHostApi );
|
|
jack_set_error_function( JackErrorCallback );
|
|
jackHostApi->jack_buffer_size = jack_get_buffer_size ( jackHostApi->jack_client );
|
|
UNLESS( !jack_set_sample_rate_callback( jackHostApi->jack_client, JackSrCb, jackHostApi ), paUnanticipatedHostError );
|
|
UNLESS( !jack_set_xrun_callback( jackHostApi->jack_client, JackXRunCb, jackHostApi ), paUnanticipatedHostError );
|
|
UNLESS( !jack_set_process_callback( jackHostApi->jack_client, JackCallback, jackHostApi ), paUnanticipatedHostError );
|
|
UNLESS( !jack_activate( jackHostApi->jack_client ), paUnanticipatedHostError );
|
|
activated = 1;
|
|
|
|
return result;
|
|
|
|
error:
|
|
if( activated )
|
|
ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 );
|
|
|
|
if( jackHostApi )
|
|
{
|
|
if( jackHostApi->jack_client )
|
|
ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 );
|
|
|
|
if( jackHostApi->deviceInfoMemory )
|
|
{
|
|
PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory );
|
|
PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory );
|
|
}
|
|
|
|
PaUtil_FreeMemory( jackHostApi );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
|
|
{
|
|
PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi;
|
|
|
|
/* note: this automatically disconnects all ports, since a deactivated
|
|
* client is not allowed to have any ports connected */
|
|
ASSERT_CALL( jack_deactivate( jackHostApi->jack_client ), 0 );
|
|
|
|
ASSERT_CALL( pthread_mutex_destroy( &jackHostApi->mtx ), 0 );
|
|
ASSERT_CALL( pthread_cond_destroy( &jackHostApi->cond ), 0 );
|
|
|
|
ASSERT_CALL( jack_client_close( jackHostApi->jack_client ), 0 );
|
|
|
|
if( jackHostApi->deviceInfoMemory )
|
|
{
|
|
PaUtil_FreeAllAllocations( jackHostApi->deviceInfoMemory );
|
|
PaUtil_DestroyAllocationGroup( jackHostApi->deviceInfoMemory );
|
|
}
|
|
|
|
PaUtil_FreeMemory( jackHostApi );
|
|
|
|
free( jackErr_ );
|
|
}
|
|
|
|
static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
|
|
const PaStreamParameters *inputParameters,
|
|
const PaStreamParameters *outputParameters,
|
|
double sampleRate )
|
|
{
|
|
int inputChannelCount = 0, outputChannelCount = 0;
|
|
PaSampleFormat inputSampleFormat, outputSampleFormat;
|
|
|
|
if( inputParameters )
|
|
{
|
|
inputChannelCount = inputParameters->channelCount;
|
|
inputSampleFormat = inputParameters->sampleFormat;
|
|
|
|
/* unless alternate device specification is supported, reject the use of
|
|
paUseHostApiSpecificDeviceSpecification */
|
|
|
|
if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
|
|
return paInvalidDevice;
|
|
|
|
/* check that input device can support inputChannelCount */
|
|
if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
|
|
return paInvalidChannelCount;
|
|
|
|
/* validate inputStreamInfo */
|
|
if( inputParameters->hostApiSpecificStreamInfo )
|
|
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
|
|
}
|
|
else
|
|
{
|
|
inputChannelCount = 0;
|
|
}
|
|
|
|
if( outputParameters )
|
|
{
|
|
outputChannelCount = outputParameters->channelCount;
|
|
outputSampleFormat = outputParameters->sampleFormat;
|
|
|
|
/* unless alternate device specification is supported, reject the use of
|
|
paUseHostApiSpecificDeviceSpecification */
|
|
|
|
if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
|
|
return paInvalidDevice;
|
|
|
|
/* check that output device can support inputChannelCount */
|
|
if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
|
|
return paInvalidChannelCount;
|
|
|
|
/* validate outputStreamInfo */
|
|
if( outputParameters->hostApiSpecificStreamInfo )
|
|
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
|
|
}
|
|
else
|
|
{
|
|
outputChannelCount = 0;
|
|
}
|
|
|
|
/*
|
|
The following check is not necessary for JACK.
|
|
|
|
- if a full duplex stream is requested, check that the combination
|
|
of input and output parameters is supported
|
|
|
|
|
|
Because the buffer adapter handles conversion between all standard
|
|
sample formats, the following checks are only required if paCustomFormat
|
|
is implemented, or under some other unusual conditions.
|
|
|
|
- check that input device can support inputSampleFormat, or that
|
|
we have the capability to convert from outputSampleFormat to
|
|
a native format
|
|
|
|
- check that output device can support outputSampleFormat, or that
|
|
we have the capability to convert from outputSampleFormat to
|
|
a native format
|
|
*/
|
|
|
|
/* check that the device supports sampleRate */
|
|
|
|
#define ABS(x) ( (x) > 0 ? (x) : -(x) )
|
|
if( ABS(sampleRate - jack_get_sample_rate(((PaJackHostApiRepresentation *) hostApi)->jack_client )) > 1 )
|
|
return paInvalidSampleRate;
|
|
#undef ABS
|
|
|
|
return paFormatIsSupported;
|
|
}
|
|
|
|
/* Basic stream initialization */
|
|
static PaError InitializeStream( PaJackStream *stream, PaJackHostApiRepresentation *hostApi, int numInputChannels,
|
|
int numOutputChannels )
|
|
{
|
|
PaError result = paNoError;
|
|
assert( stream );
|
|
|
|
memset( stream, 0, sizeof (PaJackStream) );
|
|
UNLESS( stream->stream_memory = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
|
|
stream->jack_client = hostApi->jack_client;
|
|
stream->hostApi = hostApi;
|
|
|
|
if( numInputChannels > 0 )
|
|
{
|
|
UNLESS( stream->local_input_ports =
|
|
(jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
|
|
paInsufficientMemory );
|
|
memset( stream->local_input_ports, 0, sizeof(jack_port_t*) * numInputChannels );
|
|
UNLESS( stream->remote_output_ports =
|
|
(jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numInputChannels ),
|
|
paInsufficientMemory );
|
|
memset( stream->remote_output_ports, 0, sizeof(jack_port_t*) * numInputChannels );
|
|
}
|
|
if( numOutputChannels > 0 )
|
|
{
|
|
UNLESS( stream->local_output_ports =
|
|
(jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
|
|
paInsufficientMemory );
|
|
memset( stream->local_output_ports, 0, sizeof(jack_port_t*) * numOutputChannels );
|
|
UNLESS( stream->remote_input_ports =
|
|
(jack_port_t**) PaUtil_GroupAllocateMemory( stream->stream_memory, sizeof(jack_port_t*) * numOutputChannels ),
|
|
paInsufficientMemory );
|
|
memset( stream->remote_input_ports, 0, sizeof(jack_port_t*) * numOutputChannels );
|
|
}
|
|
|
|
stream->num_incoming_connections = numInputChannels;
|
|
stream->num_outgoing_connections = numOutputChannels;
|
|
|
|
error:
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
* Free resources associated with stream, and eventually stream itself.
|
|
*
|
|
* Frees allocated memory, and closes opened pcms.
|
|
*/
|
|
static void CleanUpStream( PaJackStream *stream, int terminateStreamRepresentation, int terminateBufferProcessor )
|
|
{
|
|
int i;
|
|
assert( stream );
|
|
|
|
if( stream->isBlockingStream )
|
|
BlockingEnd( stream );
|
|
|
|
for( i = 0; i < stream->num_incoming_connections; ++i )
|
|
{
|
|
if( stream->local_input_ports[i] )
|
|
ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_input_ports[i] ), 0 );
|
|
}
|
|
for( i = 0; i < stream->num_outgoing_connections; ++i )
|
|
{
|
|
if( stream->local_output_ports[i] )
|
|
ASSERT_CALL( jack_port_unregister( stream->jack_client, stream->local_output_ports[i] ), 0 );
|
|
}
|
|
|
|
if( terminateStreamRepresentation )
|
|
PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
|
|
if( terminateBufferProcessor )
|
|
PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
|
|
|
|
if( stream->stream_memory )
|
|
{
|
|
PaUtil_FreeAllAllocations( stream->stream_memory );
|
|
PaUtil_DestroyAllocationGroup( stream->stream_memory );
|
|
}
|
|
PaUtil_FreeMemory( stream );
|
|
}
|
|
|
|
static PaError WaitCondition( PaJackHostApiRepresentation *hostApi )
|
|
{
|
|
PaError result = paNoError;
|
|
int err = 0;
|
|
PaTime pt = PaUtil_GetTime();
|
|
struct timespec ts;
|
|
|
|
ts.tv_sec = (time_t) floor( pt + 1 );
|
|
ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000);
|
|
/* XXX: Best enclose in loop, in case of spurious wakeups? */
|
|
err = pthread_cond_timedwait( &hostApi->cond, &hostApi->mtx, &ts );
|
|
|
|
/* Make sure we didn't time out */
|
|
UNLESS( err != ETIMEDOUT, paTimedOut );
|
|
UNLESS( !err, paInternalError );
|
|
|
|
error:
|
|
return result;
|
|
}
|
|
|
|
static PaError AddStream( PaJackStream *stream )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackHostApiRepresentation *hostApi = stream->hostApi;
|
|
/* Add to queue of streams that should be processed */
|
|
ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 );
|
|
if( !hostApi->jackIsDown )
|
|
{
|
|
hostApi->toAdd = stream;
|
|
/* Unlock mutex and await signal from processing thread */
|
|
result = WaitCondition( stream->hostApi );
|
|
}
|
|
ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
|
|
ENSURE_PA( result );
|
|
|
|
UNLESS( !hostApi->jackIsDown, paDeviceUnavailable );
|
|
|
|
error:
|
|
return result;
|
|
}
|
|
|
|
/* Remove stream from processing queue */
|
|
static PaError RemoveStream( PaJackStream *stream )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackHostApiRepresentation *hostApi = stream->hostApi;
|
|
|
|
/* Add to queue over streams that should be processed */
|
|
ASSERT_CALL( pthread_mutex_lock( &hostApi->mtx ), 0 );
|
|
if( !hostApi->jackIsDown )
|
|
{
|
|
hostApi->toRemove = stream;
|
|
/* Unlock mutex and await signal from processing thread */
|
|
result = WaitCondition( stream->hostApi );
|
|
}
|
|
ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
|
|
ENSURE_PA( result );
|
|
|
|
error:
|
|
return result;
|
|
}
|
|
|
|
/* Add stream to processing queue */
|
|
static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
|
|
PaStream** s,
|
|
const PaStreamParameters *inputParameters,
|
|
const PaStreamParameters *outputParameters,
|
|
double sampleRate,
|
|
unsigned long framesPerBuffer,
|
|
PaStreamFlags streamFlags,
|
|
PaStreamCallback *streamCallback,
|
|
void *userData )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackHostApiRepresentation *jackHostApi = (PaJackHostApiRepresentation*)hostApi;
|
|
PaJackStream *stream = NULL;
|
|
char *port_string = alloca( jack_port_name_size() );
|
|
unsigned long regexSz = jack_client_name_size() + 3;
|
|
char *regex_pattern = alloca( regexSz );
|
|
const char **jack_ports = NULL;
|
|
/* int jack_max_buffer_size = jack_get_buffer_size( jackHostApi->jack_client ); */
|
|
int i;
|
|
int inputChannelCount, outputChannelCount;
|
|
const double jackSr = jack_get_sample_rate( jackHostApi->jack_client );
|
|
PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0;
|
|
int bpInitialized = 0, srInitialized = 0; /* Initialized buffer processor and stream representation? */
|
|
unsigned long ofs;
|
|
|
|
/* validate platform specific flags */
|
|
if( (streamFlags & paPlatformSpecificFlags) != 0 )
|
|
return paInvalidFlag; /* unexpected platform specific flag */
|
|
if( (streamFlags & paPrimeOutputBuffersUsingStreamCallback) != 0 )
|
|
{
|
|
streamFlags &= ~paPrimeOutputBuffersUsingStreamCallback;
|
|
/*return paInvalidFlag;*/ /* This implementation does not support buffer priming */
|
|
}
|
|
|
|
if( framesPerBuffer != paFramesPerBufferUnspecified )
|
|
{
|
|
/* Jack operates with power of two buffers, and we don't support non-integer buffer adaption (yet) */
|
|
/*UNLESS( !(framesPerBuffer & (framesPerBuffer - 1)), paBufferTooBig );*/ /* TODO: Add descriptive error code? */
|
|
}
|
|
|
|
/* Preliminary checks */
|
|
|
|
if( inputParameters )
|
|
{
|
|
inputChannelCount = inputParameters->channelCount;
|
|
inputSampleFormat = inputParameters->sampleFormat;
|
|
|
|
/* unless alternate device specification is supported, reject the use of
|
|
paUseHostApiSpecificDeviceSpecification */
|
|
|
|
if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
|
|
return paInvalidDevice;
|
|
|
|
/* check that input device can support inputChannelCount */
|
|
if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
|
|
return paInvalidChannelCount;
|
|
|
|
/* validate inputStreamInfo */
|
|
if( inputParameters->hostApiSpecificStreamInfo )
|
|
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
|
|
}
|
|
else
|
|
{
|
|
inputChannelCount = 0;
|
|
}
|
|
|
|
if( outputParameters )
|
|
{
|
|
outputChannelCount = outputParameters->channelCount;
|
|
outputSampleFormat = outputParameters->sampleFormat;
|
|
|
|
/* unless alternate device specification is supported, reject the use of
|
|
paUseHostApiSpecificDeviceSpecification */
|
|
|
|
if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
|
|
return paInvalidDevice;
|
|
|
|
/* check that output device can support inputChannelCount */
|
|
if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
|
|
return paInvalidChannelCount;
|
|
|
|
/* validate outputStreamInfo */
|
|
if( outputParameters->hostApiSpecificStreamInfo )
|
|
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
|
|
}
|
|
else
|
|
{
|
|
outputChannelCount = 0;
|
|
}
|
|
|
|
/* ... check that the sample rate exactly matches the ONE acceptable rate
|
|
* A: This rate isn't necessarily constant though? */
|
|
|
|
#define ABS(x) ( (x) > 0 ? (x) : -(x) )
|
|
if( ABS(sampleRate - jackSr) > 1 )
|
|
return paInvalidSampleRate;
|
|
#undef ABS
|
|
|
|
UNLESS( stream = (PaJackStream*)PaUtil_AllocateMemory( sizeof(PaJackStream) ), paInsufficientMemory );
|
|
ENSURE_PA( InitializeStream( stream, jackHostApi, inputChannelCount, outputChannelCount ) );
|
|
|
|
/* the blocking emulation, if necessary */
|
|
stream->isBlockingStream = !streamCallback;
|
|
if( stream->isBlockingStream )
|
|
{
|
|
float latency = 0.001; /* 1ms is the absolute minimum we support */
|
|
int minimum_buffer_frames = 0;
|
|
|
|
if( inputParameters && inputParameters->suggestedLatency > latency )
|
|
latency = inputParameters->suggestedLatency;
|
|
else if( outputParameters && outputParameters->suggestedLatency > latency )
|
|
latency = outputParameters->suggestedLatency;
|
|
|
|
/* the latency the user asked for indicates the minimum buffer size in frames */
|
|
minimum_buffer_frames = (int) (latency * jack_get_sample_rate( jackHostApi->jack_client ));
|
|
|
|
/* we also need to be able to store at least three full jack buffers to avoid dropouts */
|
|
if( jackHostApi->jack_buffer_size * 3 > minimum_buffer_frames )
|
|
minimum_buffer_frames = jackHostApi->jack_buffer_size * 3;
|
|
|
|
/* setup blocking API data structures (FIXME: can fail) */
|
|
BlockingBegin( stream, minimum_buffer_frames );
|
|
|
|
/* install our own callback for the blocking API */
|
|
streamCallback = BlockingCallback;
|
|
userData = stream;
|
|
|
|
PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
|
|
&jackHostApi->blockingStreamInterface, streamCallback, userData );
|
|
}
|
|
else
|
|
{
|
|
PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
|
|
&jackHostApi->callbackStreamInterface, streamCallback, userData );
|
|
}
|
|
srInitialized = 1;
|
|
PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, jackSr );
|
|
|
|
/* create the JACK ports. We cannot connect them until audio
|
|
* processing begins */
|
|
|
|
/* Register a unique set of ports for this stream
|
|
* TODO: Robust allocation of new port names */
|
|
|
|
ofs = jackHostApi->inputBase;
|
|
for( i = 0; i < inputChannelCount; i++ )
|
|
{
|
|
snprintf( port_string, jack_port_name_size(), "in_%lu", ofs + i );
|
|
UNLESS( stream->local_input_ports[i] = jack_port_register(
|
|
jackHostApi->jack_client, port_string,
|
|
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ), paInsufficientMemory );
|
|
}
|
|
jackHostApi->inputBase += inputChannelCount;
|
|
|
|
ofs = jackHostApi->outputBase;
|
|
for( i = 0; i < outputChannelCount; i++ )
|
|
{
|
|
snprintf( port_string, jack_port_name_size(), "out_%lu", ofs + i );
|
|
UNLESS( stream->local_output_ports[i] = jack_port_register(
|
|
jackHostApi->jack_client, port_string,
|
|
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ), paInsufficientMemory );
|
|
}
|
|
jackHostApi->outputBase += outputChannelCount;
|
|
|
|
/* look up the jack_port_t's for the remote ports. We could do
|
|
* this at stream start time, but doing it here ensures the
|
|
* name lookup only happens once. */
|
|
|
|
if( inputChannelCount > 0 )
|
|
{
|
|
int err = 0;
|
|
|
|
/* ... remote output ports (that we input from) */
|
|
snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ inputParameters->device ]->name );
|
|
UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern,
|
|
NULL, JackPortIsOutput ), paUnanticipatedHostError );
|
|
for( i = 0; i < inputChannelCount && jack_ports[i]; i++ )
|
|
{
|
|
if( (stream->remote_output_ports[i] = jack_port_by_name(
|
|
jackHostApi->jack_client, jack_ports[i] )) == NULL )
|
|
{
|
|
err = 1;
|
|
break;
|
|
}
|
|
}
|
|
free( jack_ports );
|
|
UNLESS( !err, paInsufficientMemory );
|
|
|
|
/* Fewer ports than expected? */
|
|
UNLESS( i == inputChannelCount, paInternalError );
|
|
}
|
|
|
|
if( outputChannelCount > 0 )
|
|
{
|
|
int err = 0;
|
|
|
|
/* ... remote input ports (that we output to) */
|
|
snprintf( regex_pattern, regexSz, "%s:.*", hostApi->deviceInfos[ outputParameters->device ]->name );
|
|
UNLESS( jack_ports = jack_get_ports( jackHostApi->jack_client, regex_pattern,
|
|
NULL, JackPortIsInput ), paUnanticipatedHostError );
|
|
for( i = 0; i < outputChannelCount && jack_ports[i]; i++ )
|
|
{
|
|
if( (stream->remote_input_ports[i] = jack_port_by_name(
|
|
jackHostApi->jack_client, jack_ports[i] )) == 0 )
|
|
{
|
|
err = 1;
|
|
break;
|
|
}
|
|
}
|
|
free( jack_ports );
|
|
UNLESS( !err , paInsufficientMemory );
|
|
|
|
/* Fewer ports than expected? */
|
|
UNLESS( i == outputChannelCount, paInternalError );
|
|
}
|
|
|
|
ENSURE_PA( PaUtil_InitializeBufferProcessor(
|
|
&stream->bufferProcessor,
|
|
inputChannelCount,
|
|
inputSampleFormat,
|
|
paFloat32, /* hostInputSampleFormat */
|
|
outputChannelCount,
|
|
outputSampleFormat,
|
|
paFloat32, /* hostOutputSampleFormat */
|
|
jackSr,
|
|
streamFlags,
|
|
framesPerBuffer,
|
|
0, /* Ignored */
|
|
paUtilUnknownHostBufferSize, /* Buffer size may vary on JACK's discretion */
|
|
streamCallback,
|
|
userData ) );
|
|
bpInitialized = 1;
|
|
|
|
if( stream->num_incoming_connections > 0 )
|
|
stream->streamRepresentation.streamInfo.inputLatency = (jack_port_get_latency( stream->remote_output_ports[0] )
|
|
- jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */
|
|
+ PaUtil_GetBufferProcessorInputLatency( &stream->bufferProcessor )) / sampleRate;
|
|
if( stream->num_outgoing_connections > 0 )
|
|
stream->streamRepresentation.streamInfo.outputLatency = (jack_port_get_latency( stream->remote_input_ports[0] )
|
|
- jack_get_buffer_size( jackHostApi->jack_client ) /* One buffer is not counted as latency */
|
|
+ PaUtil_GetBufferProcessorOutputLatency( &stream->bufferProcessor )) / sampleRate;
|
|
|
|
stream->streamRepresentation.streamInfo.sampleRate = jackSr;
|
|
stream->t0 = jack_frame_time( jackHostApi->jack_client ); /* A: Time should run from Pa_OpenStream */
|
|
|
|
ENSURE_PA( AddStream( stream ) ); /* Add to queue over opened streams */
|
|
|
|
*s = (PaStream*)stream;
|
|
|
|
return result;
|
|
|
|
error:
|
|
if( stream )
|
|
CleanUpStream( stream, srInitialized, bpInitialized );
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
When CloseStream() is called, the multi-api layer ensures that
|
|
the stream has already been stopped or aborted.
|
|
*/
|
|
static PaError CloseStream( PaStream* s )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackStream *stream = (PaJackStream*)s;
|
|
|
|
/* Remove this stream from the processing queue */
|
|
ENSURE_PA( RemoveStream( stream ) );
|
|
|
|
error:
|
|
CleanUpStream( stream, 1, 1 );
|
|
return result;
|
|
}
|
|
|
|
static PaError RealProcess( PaJackStream *stream, jack_nframes_t frames )
|
|
{
|
|
PaError result = paNoError;
|
|
PaStreamCallbackTimeInfo timeInfo = {0,0,0};
|
|
int chn;
|
|
int framesProcessed;
|
|
const double sr = jack_get_sample_rate( stream->jack_client ); /* Shouldn't change during the process callback */
|
|
PaStreamCallbackFlags cbFlags = 0;
|
|
|
|
/* If the user has returned !paContinue from the callback we'll want to flush the internal buffers,
|
|
* when these are empty we can finally mark the stream as inactive */
|
|
if( stream->callbackResult != paContinue &&
|
|
PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
|
|
{
|
|
stream->is_active = 0;
|
|
if( stream->streamRepresentation.streamFinishedCallback )
|
|
stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
|
|
PA_DEBUG(( "%s: Callback finished\n", __FUNCTION__ ));
|
|
|
|
goto end;
|
|
}
|
|
|
|
timeInfo.currentTime = (jack_frame_time( stream->jack_client ) - stream->t0) / sr;
|
|
if( stream->num_incoming_connections > 0 )
|
|
timeInfo.inputBufferAdcTime = timeInfo.currentTime - jack_port_get_latency( stream->remote_output_ports[0] )
|
|
/ sr;
|
|
if( stream->num_outgoing_connections > 0 )
|
|
timeInfo.outputBufferDacTime = timeInfo.currentTime + jack_port_get_latency( stream->remote_input_ports[0] )
|
|
/ sr;
|
|
|
|
PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
|
|
|
|
if( stream->xrun )
|
|
{
|
|
/* XXX: Any way to tell which of these occurred? */
|
|
cbFlags = paOutputUnderflow | paInputOverflow;
|
|
stream->xrun = FALSE;
|
|
}
|
|
PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo,
|
|
cbFlags );
|
|
|
|
if( stream->num_incoming_connections > 0 )
|
|
PaUtil_SetInputFrameCount( &stream->bufferProcessor, frames );
|
|
if( stream->num_outgoing_connections > 0 )
|
|
PaUtil_SetOutputFrameCount( &stream->bufferProcessor, frames );
|
|
|
|
for( chn = 0; chn < stream->num_incoming_connections; chn++ )
|
|
{
|
|
jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*)
|
|
jack_port_get_buffer( stream->local_input_ports[chn],
|
|
frames );
|
|
|
|
PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor,
|
|
chn,
|
|
channel_buf );
|
|
}
|
|
|
|
for( chn = 0; chn < stream->num_outgoing_connections; chn++ )
|
|
{
|
|
jack_default_audio_sample_t *channel_buf = (jack_default_audio_sample_t*)
|
|
jack_port_get_buffer( stream->local_output_ports[chn],
|
|
frames );
|
|
|
|
PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor,
|
|
chn,
|
|
channel_buf );
|
|
}
|
|
|
|
framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor,
|
|
&stream->callbackResult );
|
|
/* We've specified a host buffer size mode where every frame should be consumed by the buffer processor */
|
|
assert( framesProcessed == frames );
|
|
|
|
PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
|
|
|
|
end:
|
|
return result;
|
|
}
|
|
|
|
/* Alter the processing queue if necessary */
|
|
static PaError UpdateQueue( PaJackHostApiRepresentation *hostApi )
|
|
{
|
|
PaError result = paNoError;
|
|
int queueModified = 0;
|
|
const double jackSr = jack_get_sample_rate( hostApi->jack_client );
|
|
int err;
|
|
|
|
if( (err = pthread_mutex_trylock( &hostApi->mtx )) != 0 )
|
|
{
|
|
assert( err == EBUSY );
|
|
return paNoError;
|
|
}
|
|
|
|
if( hostApi->toAdd )
|
|
{
|
|
if( hostApi->processQueue )
|
|
{
|
|
PaJackStream *node = hostApi->processQueue;
|
|
/* Advance to end of queue */
|
|
while( node->next )
|
|
node = node->next;
|
|
|
|
node->next = hostApi->toAdd;
|
|
}
|
|
else
|
|
hostApi->processQueue = (PaJackStream *)hostApi->toAdd;
|
|
|
|
/* If necessary, update stream state */
|
|
if( hostApi->toAdd->streamRepresentation.streamInfo.sampleRate != jackSr )
|
|
UpdateSampleRate( hostApi->toAdd, jackSr );
|
|
|
|
hostApi->toAdd = NULL;
|
|
queueModified = 1;
|
|
}
|
|
if( hostApi->toRemove )
|
|
{
|
|
int removed = 0;
|
|
PaJackStream *node = hostApi->processQueue, *prev = NULL;
|
|
assert( hostApi->processQueue );
|
|
|
|
while( node )
|
|
{
|
|
if( node == hostApi->toRemove )
|
|
{
|
|
if( prev )
|
|
prev->next = node->next;
|
|
else
|
|
hostApi->processQueue = (PaJackStream *)node->next;
|
|
|
|
removed = 1;
|
|
break;
|
|
}
|
|
|
|
prev = node;
|
|
node = node->next;
|
|
}
|
|
UNLESS( removed, paInternalError );
|
|
hostApi->toRemove = NULL;
|
|
PA_DEBUG(( "%s: Removed stream from processing queue\n", __FUNCTION__ ));
|
|
queueModified = 1;
|
|
}
|
|
|
|
if( queueModified )
|
|
{
|
|
/* Signal that we've done what was asked of us */
|
|
ASSERT_CALL( pthread_cond_signal( &hostApi->cond ), 0 );
|
|
}
|
|
|
|
error:
|
|
ASSERT_CALL( pthread_mutex_unlock( &hostApi->mtx ), 0 );
|
|
|
|
return result;
|
|
}
|
|
|
|
static int JackCallback( jack_nframes_t frames, void *userData )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackHostApiRepresentation *hostApi = (PaJackHostApiRepresentation *)userData;
|
|
PaJackStream *stream = NULL;
|
|
int xrun = hostApi->xrun;
|
|
hostApi->xrun = 0;
|
|
|
|
assert( hostApi );
|
|
|
|
ENSURE_PA( UpdateQueue( hostApi ) );
|
|
|
|
/* Process each stream */
|
|
stream = hostApi->processQueue;
|
|
for( ; stream; stream = stream->next )
|
|
{
|
|
if( xrun ) /* Don't override if already set */
|
|
stream->xrun = 1;
|
|
|
|
/* See if this stream is to be started */
|
|
if( stream->doStart )
|
|
{
|
|
/* If we can't obtain a lock, we'll try next time */
|
|
int err = pthread_mutex_trylock( &stream->hostApi->mtx );
|
|
if( !err )
|
|
{
|
|
if( stream->doStart ) /* Could potentially change before obtaining the lock */
|
|
{
|
|
stream->is_active = 1;
|
|
stream->doStart = 0;
|
|
PA_DEBUG(( "%s: Starting stream\n", __FUNCTION__ ));
|
|
ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 );
|
|
stream->callbackResult = paContinue;
|
|
stream->isSilenced = 0;
|
|
}
|
|
|
|
ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
|
|
}
|
|
else
|
|
assert( err == EBUSY );
|
|
}
|
|
else if( stream->doStop || stream->doAbort ) /* Should we stop/abort stream? */
|
|
{
|
|
if( stream->callbackResult == paContinue ) /* Ok, make it stop */
|
|
{
|
|
PA_DEBUG(( "%s: Stopping stream\n", __FUNCTION__ ));
|
|
stream->callbackResult = stream->doStop ? paComplete : paAbort;
|
|
}
|
|
}
|
|
|
|
if( stream->is_active )
|
|
ENSURE_PA( RealProcess( stream, frames ) );
|
|
/* If we have just entered inactive state, silence output */
|
|
if( !stream->is_active && !stream->isSilenced )
|
|
{
|
|
int i;
|
|
|
|
/* Silence buffer after entering inactive state */
|
|
PA_DEBUG(( "Silencing the output\n" ));
|
|
for( i = 0; i < stream->num_outgoing_connections; ++i )
|
|
{
|
|
jack_default_audio_sample_t *buffer = jack_port_get_buffer( stream->local_output_ports[i], frames );
|
|
memset( buffer, 0, sizeof (jack_default_audio_sample_t) * frames );
|
|
}
|
|
|
|
stream->isSilenced = 1;
|
|
}
|
|
|
|
if( stream->doStop || stream->doAbort )
|
|
{
|
|
/* See if RealProcess has acted on the request */
|
|
if( !stream->is_active ) /* Ok, signal to the main thread that we've carried out the operation */
|
|
{
|
|
/* If we can't obtain a lock, we'll try next time */
|
|
int err = pthread_mutex_trylock( &stream->hostApi->mtx );
|
|
if( !err )
|
|
{
|
|
stream->doStop = stream->doAbort = 0;
|
|
ASSERT_CALL( pthread_cond_signal( &stream->hostApi->cond ), 0 );
|
|
ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
|
|
}
|
|
else
|
|
assert( err == EBUSY );
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
static PaError StartStream( PaStream *s )
|
|
{
|
|
PaError result = paNoError;
|
|
PaJackStream *stream = (PaJackStream*)s;
|
|
int i;
|
|
|
|
/* Ready the processor */
|
|
PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
|
|
|
|
/* connect the ports */
|
|
|
|
/* NOTE: I would rather use jack_port_connect which uses jack_port_t's
|
|
* instead of port names, but it is not implemented yet. */
|
|
if( stream->num_incoming_connections > 0 )
|
|
{
|
|
for( i = 0; i < stream->num_incoming_connections; i++ )
|
|
UNLESS( jack_connect( stream->jack_client,
|
|
jack_port_name( stream->remote_output_ports[i] ),
|
|
jack_port_name( stream->local_input_ports[i] ) ) == 0, paUnanticipatedHostError );
|
|
}
|
|
|
|
if( stream->num_outgoing_connections > 0 )
|
|
{
|
|
for( i = 0; i < stream->num_outgoing_connections; i++ )
|
|
UNLESS( jack_connect( stream->jack_client,
|
|
jack_port_name( stream->local_output_ports[i] ),
|
|
jack_port_name( stream->remote_input_ports[i] ) ) == 0, paUnanticipatedHostError );
|
|
}
|
|
|
|
stream->xrun = FALSE;
|
|
|
|
/* Enable processing */
|
|
|
|
ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 );
|
|
stream->doStart = 1;
|
|
|
|
/* Wait for stream to be started */
|
|
result = WaitCondition( stream->hostApi );
|
|
/*
|
|
do
|
|
{
|
|
err = pthread_cond_timedwait( &stream->hostApi->cond, &stream->hostApi->mtx, &ts );
|
|
} while( !stream->is_active && !err );
|
|
*/
|
|
if( result != paNoError ) /* Something went wrong, call off the stream start */
|
|
{
|
|
stream->doStart = 0;
|
|
stream->is_active = 0; /* Cancel any processing */
|
|
}
|
|
ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
|
|
|
|
ENSURE_PA( result );
|
|
|
|
stream->is_running = TRUE;
|
|
PA_DEBUG(( "%s: Stream started\n", __FUNCTION__ ));
|
|
|
|
error:
|
|
return result;
|
|
}
|
|
|
|
static PaError RealStop( PaJackStream *stream, int abort )
|
|
{
|
|
PaError result = paNoError;
|
|
int i;
|
|
|
|
if( stream->isBlockingStream )
|
|
BlockingWaitEmpty ( stream );
|
|
|
|
ASSERT_CALL( pthread_mutex_lock( &stream->hostApi->mtx ), 0 );
|
|
if( abort )
|
|
stream->doAbort = 1;
|
|
else
|
|
stream->doStop = 1;
|
|
|
|
/* Wait for stream to be stopped */
|
|
result = WaitCondition( stream->hostApi );
|
|
ASSERT_CALL( pthread_mutex_unlock( &stream->hostApi->mtx ), 0 );
|
|
ENSURE_PA( result );
|
|
|
|
UNLESS( !stream->is_active, paInternalError );
|
|
|
|
PA_DEBUG(( "%s: Stream stopped\n", __FUNCTION__ ));
|
|
|
|
error:
|
|
stream->is_running = FALSE;
|
|
|
|
/* Disconnect ports belonging to this stream */
|
|
|
|
if( !stream->hostApi->jackIsDown ) /* XXX: Well? */
|
|
{
|
|
if( stream->num_incoming_connections > 0 )
|
|
{
|
|
for( i = 0; i < stream->num_incoming_connections; i++ )
|
|
UNLESS( !jack_disconnect( stream->jack_client,
|
|
jack_port_name( stream->remote_output_ports[i] ),
|
|
jack_port_name( stream->local_input_ports[i] ) ), paUnanticipatedHostError );
|
|
}
|
|
if( stream->num_outgoing_connections > 0 )
|
|
{
|
|
for( i = 0; i < stream->num_outgoing_connections; i++ )
|
|
UNLESS( !jack_disconnect( stream->jack_client,
|
|
jack_port_name( stream->local_output_ports[i] ),
|
|
jack_port_name( stream->remote_input_ports[i] ) ), paUnanticipatedHostError );
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static PaError StopStream( PaStream *s )
|
|
{
|
|
assert(s);
|
|
return RealStop( (PaJackStream *)s, 0 );
|
|
}
|
|
|
|
static PaError AbortStream( PaStream *s )
|
|
{
|
|
assert(s);
|
|
return RealStop( (PaJackStream *)s, 1 );
|
|
}
|
|
|
|
static PaError IsStreamStopped( PaStream *s )
|
|
{
|
|
PaJackStream *stream = (PaJackStream*)s;
|
|
return !stream->is_running;
|
|
}
|
|
|
|
|
|
static PaError IsStreamActive( PaStream *s )
|
|
{
|
|
PaJackStream *stream = (PaJackStream*)s;
|
|
return stream->is_active;
|
|
}
|
|
|
|
|
|
static PaTime GetStreamTime( PaStream *s )
|
|
{
|
|
PaJackStream *stream = (PaJackStream*)s;
|
|
|
|
/* A: Is this relevant?? --> TODO: what if we're recording-only? */
|
|
return (jack_frame_time( stream->jack_client ) - stream->t0) / (PaTime)jack_get_sample_rate( stream->jack_client );
|
|
}
|
|
|
|
|
|
static double GetStreamCpuLoad( PaStream* s )
|
|
{
|
|
PaJackStream *stream = (PaJackStream*)s;
|
|
return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
|
|
}
|