Commit b4e98c29 authored by Miller Puckette's avatar Miller Puckette
Browse files

some debugging on mac

parent c8470102
import os.path, copy, sys
def checkSymbol(conf, header, library=None, symbol=None, autoAdd=True, critical=False, pkgName=None):
""" Check for symbol in library, optionally look only for header.
@param conf: Configure instance.
@param header: The header file where the symbol is declared.
@param library: The library in which the symbol exists, if None it is taken to be the standard C library.
@param symbol: The symbol to look for, if None only the header will be looked up.
@param autoAdd: Automatically link with this library if check is positive.
@param critical: Raise on error?
@param pkgName: Optional name of pkg-config entry for library, to determine build parameters.
@return: True/False
origEnv = conf.env.Copy() # Copy unmodified environment so we can restore it upon error
env = conf.env
if library is None:
library = "c" # Standard library
autoAdd = False
if pkgName is not None:
origLibs = copy.copy(env.get("LIBS", None))
try: env.ParseConfig("pkg-config --silence-errors %s --cflags --libs" % pkgName)
except: pass
# I see no other way of checking that the parsing succeeded, if it did add no more linking parameters
if env.get("LIBS", None) != origLibs:
autoAdd = False
if not conf.CheckCHeader(header, include_quotes="<>"):
raise ConfigurationError("missing header %s" % header)
if symbol is not None and not conf.CheckLib(library, symbol, language="C", autoadd=autoAdd):
raise ConfigurationError("missing symbol %s in library %s" % (symbol, library))
except ConfigurationError:
conf.env = origEnv
if not critical:
return False
return True
import SCons.Errors
# Import common variables
# Could use '#' to refer to top-level SConstruct directory, but looks like env.SConsignFile doesn't interpret this at least :(
sconsDir = os.path.abspath(os.path.join("build", "scons"))
Import("Platform", "Posix", "ConfigurationError", "ApiVer")
except SCons.Errors.UserError:
# The common objects must be exported first
SConscript(os.path.join(sconsDir, "SConscript_common"))
Import("Platform", "Posix", "ConfigurationError", "ApiVer")
# This will be manipulated
env = env.Copy()
# We operate with a set of needed libraries and optional libraries, the latter stemming from host API implementations.
# For libraries of both types we record a set of values that is used to look for the library in question, during
# configuration. If the corresponding library for a host API implementation isn't found, the implementation is left out.
neededLibs = []
optionalImpls = {}
if Platform in Posix:
env.Append(CPPPATH=os.path.join("os", "unix"))
neededLibs += [("pthread", "pthread.h", "pthread_create"), ("m", "math.h", "sin")]
if env["useALSA"]:
optionalImpls["ALSA"] = ("asound", "alsa/asoundlib.h", "snd_pcm_open")
if env["useJACK"]:
optionalImpls["JACK"] = ("jack", "jack/jack.h", "jack_client_new")
if env["useOSS"]:
# TODO: It looks like the prefix for soundcard.h depends on the platform
optionalImpls["OSS"] = ("oss", "sys/soundcard.h", None)
if env["useASIHPI"]:
optionalImpls["ASIHPI"] = ("hpi", "asihpi/hpi.h", "HPI_SubSysCreate")
raise ConfigurationError("unknown platform %s" % Platform)
if Platform == "darwin":
env.Append(LINKFLAGS=["-framework CoreAudio", "-framework AudioToolBox"])
elif Platform == "cygwin":
elif Platform == "irix":
neededLibs += [("audio", "dmedia/audio.h", "alOpenPort"), ("dmedia", "dmedia/dmedia.h", "dmGetUST")]
def CheckCTypeSize(context, tp):
""" Check size of C type.
@param context: A configuration context.
@param tp: The type to check.
@return: Size of type, in bytes.
context.Message("Checking the size of C type %s..." % tp)
ret = context.TryRun("""
#include <stdio.h>
int main() {
printf("%%d", sizeof(%s));
return 0;
""" % tp, ".c")
if not ret[0]:
context.Result(" Couldn't obtain size of type %s!" % tp)
return None
assert ret[1]
sz = int(ret[1])
context.Result("%d" % sz)
return sz
if sys.byteorder == "little":
elif sys.byteorder == "big":
raise ConfigurationError("unknown byte order: %s" % sys.byteorder)
if env["enableDebugOutput"]:
# Start configuration
# Use an absolute path for conf_dir, otherwise it gets created both relative to current directory and build directory
conf = env.Configure(log_file=os.path.join(sconsDir, "sconf.log"), custom_tests={"CheckCTypeSize": CheckCTypeSize},
conf_dir=os.path.join(sconsDir, ".sconf_temp"))
conf.env.Append(CPPDEFINES=["SIZEOF_SHORT=%d" % conf.CheckCTypeSize("short")])
conf.env.Append(CPPDEFINES=["SIZEOF_INT=%d" % conf.CheckCTypeSize("int")])
conf.env.Append(CPPDEFINES=["SIZEOF_LONG=%d" % conf.CheckCTypeSize("long")])
if checkSymbol(conf, "time.h", "rt", "clock_gettime"):
if checkSymbol(conf, "time.h", symbol="nanosleep"):
if conf.CheckCHeader("sys/soundcard.h"):
if conf.CheckCHeader("linux/soundcard.h"):
if conf.CheckCHeader("machine/soundcard.h"):
# Look for needed libraries and link with them
for lib, hdr, sym in neededLibs:
checkSymbol(conf, hdr, lib, sym, critical=True)
# Look for host API libraries, if a library isn't found disable corresponding host API implementation.
for name, val in optionalImpls.items():
lib, hdr, sym = val
if checkSymbol(conf, hdr, lib, sym, critical=False, pkgName=name.lower()):
conf.env.Append(CPPDEFINES=["PA_USE_%s=1" % name.upper()])
del optionalImpls[name]
# Configuration finished
env = conf.Finish()
# PA infrastructure
CommonSources = [os.path.join("common", f) for f in "pa_allocation.c pa_converters.c pa_cpuload.c pa_dither.c pa_front.c \
pa_process.c pa_skeleton.c pa_stream.c pa_trace.c pa_debugprint.c pa_ringbuffer.c".split()]
# Host API implementations
ImplSources = []
if Platform in Posix:
ImplSources += [os.path.join("os", "unix", f) for f in "pa_unix_hostapis.c pa_unix_util.c".split()]
if "ALSA" in optionalImpls:
ImplSources.append(os.path.join("hostapi", "alsa", "pa_linux_alsa.c"))
if "JACK" in optionalImpls:
ImplSources.append(os.path.join("hostapi", "jack", "pa_jack.c"))
if "OSS" in optionalImpls:
ImplSources.append(os.path.join("hostapi", "oss", "pa_unix_oss.c"))
if "ASIHPI" in optionalImpls:
ImplSources.append(os.path.join("hostapi", "asihpi", "pa_linux_asihpi.c"))
sources = CommonSources + ImplSources
sharedLibEnv = env.Copy()
if Platform in Posix:
# Add soname to library, this is so a reference is made to the versioned library in programs linking against
sharedLibEnv.AppendUnique(SHLINKFLAGS="-Wl," % int(ApiVer.split(".")[0]))
sharedLib = sharedLibEnv.SharedLibrary(target="portaudio", source=sources)
staticLib = env.StaticLibrary(target="portaudio", source=sources)
if Platform in Posix:
prefix = env["prefix"]
includeDir = os.path.join(prefix, "include")
libDir = os.path.join(prefix, "lib")
testNames = ["patest_sine", "paqa_devs", "paqa_errs", "patest1", "patest_buffer", "patest_callbackstop", "patest_clip", \
"patest_dither", "patest_hang", "patest_in_overflow", "patest_latency", "patest_leftright", "patest_longsine", \
"patest_many", "patest_maxsines", "patest_multi_sine", "patest_out_underflow", "patest_pink", "patest_prime", \
"patest_read_record", "patest_record", "patest_ringmix", "patest_saw", "patest_sine8", "patest_sine", \
"patest_sine_time", "patest_start_stop", "patest_stop", "patest_sync", "patest_toomanysines", \
"patest_underflow", "patest_wire", "patest_write_sine", "pa_devs", "pa_fuzz", "pa_minlat", \
# The test directory ("bin") should be in the top-level PA directory
tests = [env.Program(target=os.path.join("#", "bin", name), source=[os.path.join("#", "test", name + ".c"),
staticLib]) for name in testNames]
Return("sources", "sharedLib", "staticLib", "tests", "env")
* PortAudio Portable Real-Time Audio Library
* Latest Version at:
* PortAudio v18 version of AudioScience HPI driver by Fred Gleason <>
* PortAudio v19 version of AudioScience HPI driver by Ludwig Schwardt <>
* Copyright (c) 2003 Fred Gleason
* Copyright (c) 2005,2006 Ludwig Schwardt
* 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.
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
* 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. It is also
* requested that these non-binding requests be included along with the
* license above.
* Modification History
* 12/2003 - Initial version
* 09/2005 - v19 version [rewrite]
/** @file
@ingroup hostapi_src
@brief Host API implementation supporting AudioScience cards
via the Linux HPI interface.
This is a PortAudio implementation for the AudioScience HPI Audio API
on the Linux platform. AudioScience makes a range of audio adapters customised
for the broadcasting industry, with support for both Windows and Linux.
More information on their products can be found on their website:
Documentation for the HPI API can be found at:
The Linux HPI driver itself (a kernel module + library) can be downloaded from:
<h3>Implementation strategy</h3>
*Note* Ideally, AudioScience cards should be handled by the PortAudio ALSA
implementation on Linux, as ALSA is the preferred Linux soundcard API. The existence
of this host API implementation might therefore seem a bit flawed. Unfortunately, at
the time of the creation of this implementation (June 2006), the PA ALSA implementation
could not make use of the existing AudioScience ALSA driver. PA ALSA uses the
"memory-mapped" (mmap) ALSA access mode to interact with the ALSA library, while the
AudioScience ALSA driver only supports the "read-write" access mode. The appropriate
solution to this problem is to add "read-write" support to PortAudio ALSA, thereby
extending the range of soundcards it supports (AudioScience cards are not the only
ones with this problem). Given the author's limited knowledge of ALSA and the
simplicity of the HPI API, the second-best solution was born...
The following mapping between HPI and PA was followed:
HPI subsystem => PortAudio host API
HPI adapter => nothing specific
HPI stream => PortAudio device
Each HPI stream is either input or output (not both), and can support
different channel counts, sampling rates and sample formats. It is therefore
a more natural fit to a PA device. A PA stream can therefore combine two
HPI streams (one input and one output) into a "full-duplex" stream. These
HPI streams can even be on different physical adapters. The two streams ought to be
sample-synchronised when they reside on the same adapter, as most AudioScience adapters
derive their ADC and DAC clocks from one master clock. When combining two adapters
into one full-duplex stream, however, the use of a word clock connection between the
adapters is strongly recommended.
The HPI interface is inherently blocking, making use of read and write calls to
transfer data between user buffers and driver buffers. The callback interface therefore
requires a helper thread ("callback engine") which periodically transfers data (one thread
per PA stream, in fact). The current implementation explicitly sleeps via Pa_Sleep() until
enough samples can be transferred (select() or poll() would be better, but currently seems
impossible...). The thread implementation makes use of the Unix thread helper functions
and some pthread calls here and there. If a unified PA thread exists, this host API
implementation might also compile on Windows, as this is the only real Linux-specific
part of the code.
There is no inherent fixed buffer size in the HPI interface, as in some other host APIs.
The PortAudio implementation contains a buffer that is allocated during OpenStream and
used to transfer data between the callback and the HPI driver buffer. The size of this
buffer is quite flexible and is derived from latency suggestions and matched to the
requested callback buffer size as far as possible. It can become quite huge, as the
AudioScience cards are typically geared towards higher-latency applications and contain
large hardware buffers.
The HPI interface natively supports most common sample formats and sample rates (some
conversion is done on the adapter itself).
Stream time is measured based on the number of processed frames, which is adjusted by the
number of frames currently buffered by the HPI driver.
There is basic support for detecting overflow and underflow. The HPI interface does not
explicitly indicate this, so thresholds on buffer levels are used in combination with
stream state. Recovery from overflow and underflow is left to the PA client.
Blocking streams are also implemented. It makes use of the same polling routines that
the callback interface uses, in order to prevent the allocation of variable-sized
buffers during reading and writing. The framesPerBuffer parameter is therefore still
relevant, and this can be increased in the blocking case to improve efficiency.
The implementation contains extensive reporting macros (slightly modified PA_ENSURE and
PA_UNLESS versions) and a useful stream dump routine to provide debugging feedback.
Output buffer priming via the user callback (i.e. paPrimeOutputBuffersUsingStreamCallback
and friends) is not implemented yet. All output is primed with silence.
Please send bug reports etc. to Ludwig Schwardt <>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* strlen() */
#include <pthread.h> /* pthreads and friends */
#include <assert.h> /* assert */
#include <math.h> /* ceil, floor */
#include <asihpi/hpi.h> /* HPI API */
#include "portaudio.h" /* PortAudio API */
#include "pa_util.h" /* PA_DEBUG, other small utilities */
#include "pa_unix_util.h" /* Unix threading utilities */
#include "pa_allocation.h" /* Group memory allocation */
#include "pa_hostapi.h" /* Host API structs */
#include "pa_stream.h" /* Stream interface structs */
#include "pa_cpuload.h" /* CPU load measurer */
#include "pa_process.h" /* Buffer processor */
#include "pa_converters.h" /* PaUtilZeroer */
#include "pa_debugprint.h"
/* -------------------------------------------------------------------------- */
* Defines
/* Error reporting and assertions */
/** Evaluate expression, and return on any PortAudio errors */
#define PA_ENSURE_(expr) \
do { \
PaError paError = (expr); \
if( UNLIKELY( paError < paNoError ) ) \
{ \
PA_DEBUG(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
result = paError; \
goto error; \
} \
} while (0);
/** Assert expression, else return the provided PaError */
#define PA_UNLESS_(expr, paError) \
do { \
if( UNLIKELY( (expr) == 0 ) ) \
{ \
PA_DEBUG(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
result = (paError); \
goto error; \
} \
} while( 0 );
/** Check return value of HPI function, and map it to PaError */
#define PA_ASIHPI_UNLESS_(expr, paError) \
do { \
HW16 hpiError = (expr); \
/* If HPI error occurred */ \
if( UNLIKELY( hpiError ) ) \
{ \
char szError[256]; \
HPI_GetErrorText( hpiError, szError ); \
PA_DEBUG(( "HPI error %d occurred: %s\n", hpiError, szError )); \
/* This message will always be displayed, even if debug info is disabled */ \
PA_DEBUG(( "Expression '" #expr "' failed in '" __FILE__ "', line: " STRINGIZE( __LINE__ ) "\n" )); \
if( (paError) == paUnanticipatedHostError ) \
{ \
PA_DEBUG(( "Host error description: %s\n", szError )); \
/* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \
if( pthread_equal( pthread_self(), paUnixMainThread ) ) \
{ \
PaUtil_SetLastHostErrorInfo( paInDevelopment, hpiError, szError ); \
} \
} \
/* If paNoError is specified, continue as usual */ \
/* (useful if you only want to print out the debug messages above) */ \
if( (paError) < 0 ) \
{ \
result = (paError); \
goto error; \
} \
} \
} while( 0 );
/** Report HPI error code and text */
#define PA_ASIHPI_REPORT_ERROR_(hpiErrorCode) \
do { \
char szError[256]; \
HPI_GetErrorText( hpiError, szError ); \
PA_DEBUG(( "HPI error %d occurred: %s\n", hpiError, szError )); \
/* PaUtil_SetLastHostErrorInfo should only be used in the main thread */ \
if( pthread_equal( pthread_self(), paUnixMainThread ) ) \
{ \
PaUtil_SetLastHostErrorInfo( paInDevelopment, (hpiErrorCode), szError ); \
} \
} while( 0 );
/* Defaults */
/** Sample formats available natively on AudioScience hardware */
#define PA_ASIHPI_AVAILABLE_FORMATS_ (paFloat32 | paInt32 | paInt24 | paInt16 | paUInt8)
/** Enable background bus mastering (BBM) for buffer transfers, if available (see HPI docs) */
#define PA_ASIHPI_USE_BBM_ 1
/** Minimum number of frames in HPI buffer (for either data or available space).
If buffer contains less data/space, it indicates xrun or completion. */
#define PA_ASIHPI_MIN_FRAMES_ 1152
/** Minimum polling interval in milliseconds, which determines minimum host buffer size */
/* -------------------------------------------------------------------------- */
* Structures
/** Host API global data */
typedef struct PaAsiHpiHostApiRepresentation
/* PortAudio "base class" - keep the baseRep first! (C-style inheritance) */
PaUtilHostApiRepresentation baseHostApiRep;
PaUtilStreamInterface callbackStreamInterface;
PaUtilStreamInterface blockingStreamInterface;
PaUtilAllocationGroup *allocations;
/* implementation specific data goes here */
PaHostApiIndex hostApiIndex;
/** HPI subsystem pointer */
/** Device data */
typedef struct PaAsiHpiDeviceInfo
/* PortAudio "base class" - keep the baseRep first! (C-style inheritance) */
/** Common PortAudio device information */
PaDeviceInfo baseDeviceInfo;
/* implementation specific data goes here */
/** HPI subsystem (required for most HPI calls) */
/** Adapter index */
HW16 adapterIndex;
/** Adapter model number (hex) */
HW16 adapterType;
/** Adapter HW/SW version */
HW16 adapterVersion;
/** Adapter serial number */
HW32 adapterSerialNumber;
/** Stream number */
HW16 streamIndex;
/** 0=Input, 1=Output (HPI streams are either input or output but not both) */
HW16 streamIsOutput;
/** Stream state as defined by PortAudio.
It seems that the host API implementation has to keep track of the PortAudio stream state.
Please note that this is NOT the same as the state of the underlying HPI stream. By separating
these two concepts, a lot of flexibility is gained. There is a rough match between the two,
of course, but forcing a precise match is difficult. For example, HPI_STATE_DRAINED can occur
during the Active state of PortAudio (due to underruns) and also during CallBackFinished in
the case of an output stream. Similarly, HPI_STATE_STOPPED mostly coincides with the Stopped
PortAudio state, by may also occur in the CallbackFinished state when recording is finished.
Here is a rough match-up:
PortAudio state => HPI state
--------------- ---------
typedef enum PaAsiHpiStreamState
/** Stream component data (associated with one direction, i.e. either input or output) */
typedef struct PaAsiHpiStreamComponent
/** Device information (HPI handles, etc) */
PaAsiHpiDeviceInfo *hpiDevice;
/** Stream handle, as passed to HPI interface.
HACK: we assume types HPI_HISTREAM and HPI_HOSTREAM are the same...
(both are HW32 up to version 3.00 of ASIHPI, and hopefully they stay that way) */
/** Stream format, as passed to HPI interface */
HPI_FORMAT hpiFormat;
/** Number of bytes per frame, derived from hpiFormat and saved for convenience */
HW32 bytesPerFrame;
/** Size of hardware (on-card) buffer of stream in bytes */
HW32 hardwareBufferSize;
/** Size of host (BBM) buffer of stream in bytes (if used) */
HW32 hostBufferSize;
/** Upper limit on the utilization of output stream buffer (both hardware and host).
This prevents large latencies in an output-only stream with a potentially huge buffer
and a fast data generator, which would otherwise keep the hardware buffer filled to
capacity. See also the "Hardware Buffering=off" option in the AudioScience WAV driver. */
HW32 outputBufferCap;
/** Sample buffer (halfway station between HPI and buffer processor) */
HW8 *tempBuffer;
/** Sample buffer size, in bytes */
HW32 tempBufferSize;
/** Stream data */
typedef struct PaAsiHpiStream
/* PortAudio "base class" - keep the baseRep first! (C-style inheritance) */
PaUtilStreamRepresentation baseStreamRep;
PaUtilCpuLoadMeasurer cpuLoadMeasurer;
PaUtilBufferProcessor bufferProcessor;
PaUtilAllocationGroup *allocations;
/* implementation specific data goes here */
/** Separate structs for input and output sides of stream */
PaAsiHpiStreamComponent *input, *output;
/** Polling interval (in milliseconds) */
HW32 pollingInterval;
/** Are we running in callback mode? */
int callbackMode;
/** Number of frames to transfer at a time to/from HPI */
unsigned long maxFramesPerHostBuffer;
/** Indicates that the stream is in the paNeverDropInput mode */
int neverDropInput;
/** Contains copy of user buffers, used by blocking interface to transfer non-interleaved data.
It went here instead of to each stream component, as the stream component buffer setup in
PaAsiHpi_SetupBuffers doesn't know the stream details such as callbackMode.
(Maybe a problem later if ReadStream and WriteStream happens concurrently on same stream.) */
void **blockingUserBufferCopy;
/* Thread-related variables */
/** Helper thread which will deliver data to user callback */
PaUnixThread thread;
/** PortAudio stream state (Active/Stopped/CallbackFinished) */
volatile sig_atomic_t state;
/** Hard abort, i.e. drop frames? */
volatile sig_atomic_t callbackAbort;
/** True if stream stopped via exiting callback with paComplete/paAbort flag
(as opposed to explicit call to StopStream/AbortStream) */
volatile sig_atomic_t callbackFinished;
/** Stream state information, collected together for convenience */
typedef struct PaAsiHpiStreamInfo
/** HPI stream state (HPI_STATE_STOPPED, HPI_STATE_PLAYING, etc.) */
HW16 state;