summaryrefslogtreecommitdiff
path: root/portaudio/src/hostapi/asihpi
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2022-08-25 14:54:53 -0500
committersanine <sanine.not@pm.me>2022-08-25 14:54:53 -0500
commit37c97e345d12f95dde44e1d1a4c2f2aadd4615bc (patch)
treee1bb25bc855883062bdd7847ff2c04290f71c840 /portaudio/src/hostapi/asihpi
parent5634c7b04da619669f2f29f6798c03982be05180 (diff)
add initial structure
Diffstat (limited to 'portaudio/src/hostapi/asihpi')
-rw-r--r--portaudio/src/hostapi/asihpi/pa_linux_asihpi.c2893
1 files changed, 2893 insertions, 0 deletions
diff --git a/portaudio/src/hostapi/asihpi/pa_linux_asihpi.c b/portaudio/src/hostapi/asihpi/pa_linux_asihpi.c
new file mode 100644
index 0000000..79eb1d7
--- /dev/null
+++ b/portaudio/src/hostapi/asihpi/pa_linux_asihpi.c
@@ -0,0 +1,2893 @@
+/*
+ * $Id:$
+ * PortAudio Portable Real-Time Audio Library
+ * Latest Version at: http://www.portaudio.com
+ * AudioScience HPI implementation by Fred Gleason, Ludwig Schwardt and
+ * Eliot Blennerhassett
+ *
+ * Copyright (c) 2003 Fred Gleason <fredg@salemradiolabs.com>
+ * Copyright (c) 2005,2006 Ludwig Schwardt <schwardt@sun.ac.za>
+ * Copyright (c) 2011 Eliot Blennerhassett <eblennerhassett@audioscience.com>
+ *
+ * Based on the Open Source API proposed by Ross Bencina
+ * Copyright (c) 1999-2008 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.
+ *
+ * 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.
+ */
+
+/*
+ * 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.
+
+ <h3>Overview</h3>
+
+ 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:
+
+ http://www.audioscience.com
+
+ Documentation for the HPI API can be found at:
+
+ http://www.audioscience.com/internet/download/sdk/hpi_usermanual_html/html/index.html
+
+ The Linux HPI driver itself (a kernel module + library) can be downloaded from:
+
+ http://www.audioscience.com/internet/download/linux_drivers.htm
+
+ <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.
+ */
+
+#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 { \
+ hpi_err_t 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 */
+#define PA_ASIHPI_MIN_POLLING_INTERVAL_ 10
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ * 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;
+}
+PaAsiHpiHostApiRepresentation;
+
+
+/** 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 */
+
+ /** Adapter index */
+ uint16_t adapterIndex;
+ /** Adapter model number (hex) */
+ uint16_t adapterType;
+ /** Adapter HW/SW version */
+ uint16_t adapterVersion;
+ /** Adapter serial number */
+ uint32_t adapterSerialNumber;
+ /** Stream number */
+ uint16_t streamIndex;
+ /** 0=Input, 1=Output (HPI streams are either input or output but not both) */
+ uint16_t streamIsOutput;
+}
+PaAsiHpiDeviceInfo;
+
+
+/** 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
+ --------------- ---------
+ Active => HPI_STATE_RECORDING, HPI_STATE_PLAYING, (HPI_STATE_DRAINED)
+ Stopped => HPI_STATE_STOPPED
+ CallbackFinished => HPI_STATE_STOPPED, HPI_STATE_DRAINED */
+typedef enum PaAsiHpiStreamState
+{
+ paAsiHpiStoppedState=0,
+ paAsiHpiActiveState=1,
+ paAsiHpiCallbackFinishedState=2
+}
+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. */
+ hpi_handle_t hpiStream;
+ /** Stream format, as passed to HPI interface */
+ struct hpi_format hpiFormat;
+ /** Number of bytes per frame, derived from hpiFormat and saved for convenience */
+ uint32_t bytesPerFrame;
+ /** Size of hardware (on-card) buffer of stream in bytes */
+ uint32_t hardwareBufferSize;
+ /** Size of host (BBM) buffer of stream in bytes (if used) */
+ uint32_t 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. */
+ uint32_t outputBufferCap;
+ /** Sample buffer (halfway station between HPI and buffer processor) */
+ uint8_t *tempBuffer;
+ /** Sample buffer size, in bytes */
+ uint32_t tempBufferSize;
+}
+PaAsiHpiStreamComponent;
+
+
+/** 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) */
+ uint32_t 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;
+}
+PaAsiHpiStream;
+
+
+/** Stream state information, collected together for convenience */
+typedef struct PaAsiHpiStreamInfo
+{
+ /** HPI stream state (HPI_STATE_STOPPED, HPI_STATE_PLAYING, etc.) */
+ uint16_t state;
+ /** Size (in bytes) of recording/playback data buffer in HPI driver */
+ uint32_t bufferSize;
+ /** Amount of data (in bytes) available in the buffer */
+ uint32_t dataSize;
+ /** Number of frames played/recorded since last stream reset */
+ uint32_t frameCounter;
+ /** Amount of data (in bytes) in hardware (on-card) buffer.
+ This differs from dataSize if bus mastering (BBM) is used, which introduces another
+ driver-level buffer to which dataSize/bufferSize then refers. */
+ uint32_t auxDataSize;
+ /** Total number of data frames currently buffered by HPI driver (host + hw buffers) */
+ uint32_t totalBufferedData;
+ /** Size of immediately available data (for input) or space (for output) in frames.
+ This only checks the first-level buffer (typically host buffer). This amount can be
+ transferred immediately. */
+ uint32_t availableFrames;
+ /** Indicates that hardware buffer is getting too full */
+ int overflow;
+ /** Indicates that hardware buffer is getting too empty */
+ int underflow;
+}
+PaAsiHpiStreamInfo;
+
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Function prototypes
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+ /* The only exposed function in the entire host API implementation */
+ PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index );
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate );
+
+/* Stream prototypes */
+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 *s );
+static PaError StartStream( PaStream *s );
+static PaError StopStream( PaStream *s );
+static PaError AbortStream( PaStream *s );
+static PaError IsStreamStopped( PaStream *s );
+static PaError IsStreamActive( PaStream *s );
+static PaTime GetStreamTime( PaStream *s );
+static double GetStreamCpuLoad( PaStream *s );
+
+/* Blocking prototypes */
+static PaError ReadStream( PaStream *s, void *buffer, unsigned long frames );
+static PaError WriteStream( PaStream *s, const void *buffer, unsigned long frames );
+static signed long GetStreamReadAvailable( PaStream *s );
+static signed long GetStreamWriteAvailable( PaStream *s );
+
+/* Callback prototypes */
+static void *CallbackThreadFunc( void *userData );
+
+/* Functions specific to this API */
+static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostApi );
+static uint16_t PaAsiHpi_PaToHpiFormat( PaSampleFormat paFormat );
+static PaSampleFormat PaAsiHpi_HpiToPaFormat( uint16_t hpiFormat );
+static PaError PaAsiHpi_CreateFormat( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *parameters, double sampleRate,
+ PaAsiHpiDeviceInfo **hpiDevice, struct hpi_format *hpiFormat );
+static PaError PaAsiHpi_OpenInput( struct PaUtilHostApiRepresentation *hostApi,
+ const PaAsiHpiDeviceInfo *hpiDevice, const struct hpi_format *hpiFormat,
+ hpi_handle_t *hpiStream );
+static PaError PaAsiHpi_OpenOutput( struct PaUtilHostApiRepresentation *hostApi,
+ const PaAsiHpiDeviceInfo *hpiDevice, const struct hpi_format *hpiFormat,
+ hpi_handle_t *hpiStream );
+static PaError PaAsiHpi_GetStreamInfo( PaAsiHpiStreamComponent *streamComp, PaAsiHpiStreamInfo *info );
+static void PaAsiHpi_StreamComponentDump( PaAsiHpiStreamComponent *streamComp, PaAsiHpiStream *stream );
+static void PaAsiHpi_StreamDump( PaAsiHpiStream *stream );
+static PaError PaAsiHpi_SetupBuffers( PaAsiHpiStreamComponent *streamComp, uint32_t pollingInterval,
+ unsigned long framesPerPaHostBuffer, PaTime suggestedLatency );
+static PaError PaAsiHpi_PrimeOutputWithSilence( PaAsiHpiStream *stream );
+static PaError PaAsiHpi_StartStream( PaAsiHpiStream *stream, int outputPrimed );
+static PaError PaAsiHpi_StopStream( PaAsiHpiStream *stream, int abort );
+static PaError PaAsiHpi_ExplicitStop( PaAsiHpiStream *stream, int abort );
+static void PaAsiHpi_OnThreadExit( void *userData );
+static PaError PaAsiHpi_WaitForFrames( PaAsiHpiStream *stream, unsigned long *framesAvail,
+ PaStreamCallbackFlags *cbFlags );
+static void PaAsiHpi_CalculateTimeInfo( PaAsiHpiStream *stream, PaStreamCallbackTimeInfo *timeInfo );
+static PaError PaAsiHpi_BeginProcessing( PaAsiHpiStream* stream, unsigned long* numFrames,
+ PaStreamCallbackFlags *cbFlags );
+static PaError PaAsiHpi_EndProcessing( PaAsiHpiStream *stream, unsigned long numFrames,
+ PaStreamCallbackFlags *cbFlags );
+
+/* ==========================================================================
+ * ============================= IMPLEMENTATION =============================
+ * ========================================================================== */
+
+/* --------------------------- Host API Interface --------------------------- */
+
+/** Enumerate all PA devices (= HPI streams).
+ This compiles a list of all HPI adapters, and registers a PA device for each input and
+ output stream it finds. Most errors are ignored, as missing or erroneous devices are
+ simply skipped.
+
+ @param hpiHostApi Pointer to HPI host API struct
+
+ @return PortAudio error code (only paInsufficientMemory in practice)
+ */
+static PaError PaAsiHpi_BuildDeviceList( PaAsiHpiHostApiRepresentation *hpiHostApi )
+{
+ PaError result = paNoError;
+ PaUtilHostApiRepresentation *hostApi = &hpiHostApi->baseHostApiRep;
+ PaHostApiInfo *baseApiInfo = &hostApi->info;
+ PaAsiHpiDeviceInfo *hpiDeviceList;
+ int numAdapters;
+ hpi_err_t hpiError = 0;
+ int i, j, deviceCount = 0, deviceIndex = 0;
+
+ assert( hpiHostApi );
+
+ /* Errors not considered critical here (subsystem may report 0 devices), but report them */
+ /* in debug mode. */
+ PA_ASIHPI_UNLESS_( HPI_SubSysGetNumAdapters( NULL, &numAdapters), paNoError );
+
+ for( i=0; i < numAdapters; ++i )
+ {
+ uint16_t inStreams, outStreams;
+ uint16_t version;
+ uint32_t serial;
+ uint16_t type;
+ uint32_t idx;
+
+ hpiError = HPI_SubSysGetAdapter(NULL, i, &idx, &type);
+ if (hpiError)
+ continue;
+
+ /* Try to open adapter */
+ hpiError = HPI_AdapterOpen( NULL, idx );
+ /* Report error and skip to next device on failure */
+ if( hpiError )
+ {
+ PA_ASIHPI_REPORT_ERROR_( hpiError );
+ continue;
+ }
+ hpiError = HPI_AdapterGetInfo( NULL, idx, &outStreams, &inStreams,
+ &version, &serial, &type );
+ /* Skip to next device on failure */
+ if( hpiError )
+ {
+ PA_ASIHPI_REPORT_ERROR_( hpiError );
+ continue;
+ }
+ else
+ {
+ /* Assign default devices if available and increment device count */
+ if( (baseApiInfo->defaultInputDevice == paNoDevice) && (inStreams > 0) )
+ baseApiInfo->defaultInputDevice = deviceCount;
+ deviceCount += inStreams;
+ if( (baseApiInfo->defaultOutputDevice == paNoDevice) && (outStreams > 0) )
+ baseApiInfo->defaultOutputDevice = deviceCount;
+ deviceCount += outStreams;
+ }
+ }
+
+ /* Register any discovered devices */
+ if( deviceCount > 0 )
+ {
+ /* Memory allocation */
+ PA_UNLESS_( hostApi->deviceInfos = (PaDeviceInfo**) PaUtil_GroupAllocateMemory(
+ hpiHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ),
+ paInsufficientMemory );
+ /* Allocate all device info structs in a contiguous block */
+ PA_UNLESS_( hpiDeviceList = (PaAsiHpiDeviceInfo*) PaUtil_GroupAllocateMemory(
+ hpiHostApi->allocations, sizeof(PaAsiHpiDeviceInfo) * deviceCount ),
+ paInsufficientMemory );
+
+ /* Now query devices again for information */
+ for( i=0; i < numAdapters; ++i )
+ {
+ uint16_t inStreams, outStreams;
+ uint16_t version;
+ uint32_t serial;
+ uint16_t type;
+ uint32_t idx;
+
+ hpiError = HPI_SubSysGetAdapter( NULL, i, &idx, &type );
+ if (hpiError)
+ continue;
+
+ /* Assume adapter is still open from previous round */
+ hpiError = HPI_AdapterGetInfo( NULL, idx,
+ &outStreams, &inStreams, &version, &serial, &type );
+ /* Report error and skip to next device on failure */
+ if( hpiError )
+ {
+ PA_ASIHPI_REPORT_ERROR_( hpiError );
+ continue;
+ }
+ else
+ {
+ PA_DEBUG(( "Found HPI Adapter ID=%4X Idx=%d #In=%d #Out=%d S/N=%d HWver=%c%d DSPver=%03d\n",
+ type, idx, inStreams, outStreams, serial,
+ ((version>>3)&0xf)+'A', /* Hw version major */
+ version&0x7, /* Hw version minor */
+ ((version>>13)*100)+((version>>7)&0x3f) /* DSP code version */
+ ));
+ }
+
+ /* First add all input streams as devices */
+ for( j=0; j < inStreams; ++j )
+ {
+ PaAsiHpiDeviceInfo *hpiDevice = &hpiDeviceList[deviceIndex];
+ PaDeviceInfo *baseDeviceInfo = &hpiDevice->baseDeviceInfo;
+ char srcName[72];
+ char *deviceName;
+
+ memset( hpiDevice, 0, sizeof(PaAsiHpiDeviceInfo) );
+ /* Set implementation-specific device details */
+ hpiDevice->adapterIndex = idx;
+ hpiDevice->adapterType = type;
+ hpiDevice->adapterVersion = version;
+ hpiDevice->adapterSerialNumber = serial;
+ hpiDevice->streamIndex = j;
+ hpiDevice->streamIsOutput = 0;
+ /* Set common PortAudio device stats */
+ baseDeviceInfo->structVersion = 2;
+ /* Make sure name string is owned by API info structure */
+ sprintf( srcName,
+ "Adapter %d (%4X) - Input Stream %d", i+1, type, j+1 );
+ PA_UNLESS_( deviceName = (char *) PaUtil_GroupAllocateMemory(
+ hpiHostApi->allocations, strlen(srcName) + 1 ), paInsufficientMemory );
+ strcpy( deviceName, srcName );
+ baseDeviceInfo->name = deviceName;
+ baseDeviceInfo->hostApi = hpiHostApi->hostApiIndex;
+ baseDeviceInfo->maxInputChannels = HPI_MAX_CHANNELS;
+ baseDeviceInfo->maxOutputChannels = 0;
+ /* Default latency values for interactive performance */
+ baseDeviceInfo->defaultLowInputLatency = 0.01;
+ baseDeviceInfo->defaultLowOutputLatency = -1.0;
+ /* Default latency values for robust non-interactive applications (eg. playing sound files) */
+ baseDeviceInfo->defaultHighInputLatency = 0.2;
+ baseDeviceInfo->defaultHighOutputLatency = -1.0;
+ /* HPI interface can actually handle any sampling rate to 1 Hz accuracy,
+ * so this default is as good as any */
+ baseDeviceInfo->defaultSampleRate = 44100;
+
+ /* Store device in global PortAudio list */
+ hostApi->deviceInfos[deviceIndex++] = (PaDeviceInfo *) hpiDevice;
+ }
+
+ /* Now add all output streams as devices (I know, the repetition is painful) */
+ for( j=0; j < outStreams; ++j )
+ {
+ PaAsiHpiDeviceInfo *hpiDevice = &hpiDeviceList[deviceIndex];
+ PaDeviceInfo *baseDeviceInfo = &hpiDevice->baseDeviceInfo;
+ char srcName[72];
+ char *deviceName;
+
+ memset( hpiDevice, 0, sizeof(PaAsiHpiDeviceInfo) );
+ /* Set implementation-specific device details */
+ hpiDevice->adapterIndex = idx;
+ hpiDevice->adapterType = type;
+ hpiDevice->adapterVersion = version;
+ hpiDevice->adapterSerialNumber = serial;
+ hpiDevice->streamIndex = j;
+ hpiDevice->streamIsOutput = 1;
+ /* Set common PortAudio device stats */
+ baseDeviceInfo->structVersion = 2;
+ /* Make sure name string is owned by API info structure */
+ sprintf( srcName,
+ "Adapter %d (%4X) - Output Stream %d", i+1, type, j+1 );
+ PA_UNLESS_( deviceName = (char *) PaUtil_GroupAllocateMemory(
+ hpiHostApi->allocations, strlen(srcName) + 1 ), paInsufficientMemory );
+ strcpy( deviceName, srcName );
+ baseDeviceInfo->name = deviceName;
+ baseDeviceInfo->hostApi = hpiHostApi->hostApiIndex;
+ baseDeviceInfo->maxInputChannels = 0;
+ baseDeviceInfo->maxOutputChannels = HPI_MAX_CHANNELS;
+ /* Default latency values for interactive performance. */
+ baseDeviceInfo->defaultLowInputLatency = -1.0;
+ baseDeviceInfo->defaultLowOutputLatency = 0.01;
+ /* Default latency values for robust non-interactive applications (eg. playing sound files). */
+ baseDeviceInfo->defaultHighInputLatency = -1.0;
+ baseDeviceInfo->defaultHighOutputLatency = 0.2;
+ /* HPI interface can actually handle any sampling rate to 1 Hz accuracy,
+ * so this default is as good as any */
+ baseDeviceInfo->defaultSampleRate = 44100;
+
+ /* Store device in global PortAudio list */
+ hostApi->deviceInfos[deviceIndex++] = (PaDeviceInfo *) hpiDevice;
+ }
+ }
+ }
+
+ /* Finally acknowledge checked devices */
+ baseApiInfo->deviceCount = deviceIndex;
+
+error:
+ return result;
+}
+
+
+/** Initialize host API implementation.
+ This is the only function exported beyond this file. It is called by PortAudio to initialize
+ the host API. It stores API info, finds and registers all devices, and sets up callback and
+ blocking interfaces.
+
+ @param hostApi Pointer to host API struct
+
+ @param hostApiIndex Index of current (HPI) host API
+
+ @return PortAudio error code
+ */
+PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
+{
+ PaError result = paNoError;
+ PaAsiHpiHostApiRepresentation *hpiHostApi = NULL;
+ PaHostApiInfo *baseApiInfo;
+
+ /* Try to initialize HPI subsystem */
+ if (!HPI_SubSysCreate())
+ {
+ /* the V19 development docs say that if an implementation
+ * detects that it cannot be used, it should return a NULL
+ * interface and paNoError */
+ PA_DEBUG(( "Could not open HPI interface\n" ));
+
+ *hostApi = NULL;
+ return paNoError;
+ }
+ else
+ {
+ uint32_t hpiVersion;
+ PA_ASIHPI_UNLESS_( HPI_SubSysGetVersionEx( NULL, &hpiVersion ), paUnanticipatedHostError );
+ PA_DEBUG(( "HPI interface v%d.%02d.%02d\n",
+ hpiVersion >> 16, (hpiVersion >> 8) & 0x0F, (hpiVersion & 0x0F) ));
+ }
+
+ /* Allocate host API structure */
+ PA_UNLESS_( hpiHostApi = (PaAsiHpiHostApiRepresentation*) PaUtil_AllocateMemory(
+ sizeof(PaAsiHpiHostApiRepresentation) ), paInsufficientMemory );
+ PA_UNLESS_( hpiHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory );
+
+ hpiHostApi->hostApiIndex = hostApiIndex;
+
+ *hostApi = &hpiHostApi->baseHostApiRep;
+ baseApiInfo = &((*hostApi)->info);
+ /* Fill in common API details */
+ baseApiInfo->structVersion = 1;
+ baseApiInfo->type = paAudioScienceHPI;
+ baseApiInfo->name = "AudioScience HPI";
+ baseApiInfo->deviceCount = 0;
+ baseApiInfo->defaultInputDevice = paNoDevice;
+ baseApiInfo->defaultOutputDevice = paNoDevice;
+
+ PA_ENSURE_( PaAsiHpi_BuildDeviceList( hpiHostApi ) );
+
+ (*hostApi)->Terminate = Terminate;
+ (*hostApi)->OpenStream = OpenStream;
+ (*hostApi)->IsFormatSupported = IsFormatSupported;
+
+ PaUtil_InitializeStreamInterface( &hpiHostApi->callbackStreamInterface, CloseStream, StartStream,
+ StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+ GetStreamTime, GetStreamCpuLoad,
+ PaUtil_DummyRead, PaUtil_DummyWrite,
+ PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable );
+
+ PaUtil_InitializeStreamInterface( &hpiHostApi->blockingStreamInterface, CloseStream, StartStream,
+ StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+ GetStreamTime, PaUtil_DummyGetCpuLoad,
+ ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
+
+ /* Store identity of main thread */
+ PA_ENSURE_( PaUnixThreading_Initialize() );
+
+ return result;
+error:
+ if (hpiHostApi)
+ PaUtil_FreeMemory( hpiHostApi );
+ return result;
+}
+
+
+/** Terminate host API implementation.
+ This closes all HPI adapters and frees the HPI subsystem. It also frees the host API struct
+ memory. It should be called once for every PaAsiHpi_Initialize call.
+
+ @param Pointer to host API struct
+ */
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+ PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+ int i;
+ PaError result = paNoError;
+
+ if( hpiHostApi )
+ {
+ /* Get rid of HPI-specific structures */
+ uint16_t lastAdapterIndex = HPI_MAX_ADAPTERS;
+ /* Iterate through device list and close adapters */
+ for( i=0; i < hostApi->info.deviceCount; ++i )
+ {
+ PaAsiHpiDeviceInfo *hpiDevice = (PaAsiHpiDeviceInfo *) hostApi->deviceInfos[ i ];
+ /* Close adapter only if it differs from previous one */
+ if( hpiDevice->adapterIndex != lastAdapterIndex )
+ {
+ /* Ignore errors (report only during debugging) */
+ PA_ASIHPI_UNLESS_( HPI_AdapterClose( NULL,
+ hpiDevice->adapterIndex ), paNoError );
+ lastAdapterIndex = hpiDevice->adapterIndex;
+ }
+ }
+ /* Finally dismantle HPI subsystem */
+ HPI_SubSysFree( NULL );
+
+ if( hpiHostApi->allocations )
+ {
+ PaUtil_FreeAllAllocations( hpiHostApi->allocations );
+ PaUtil_DestroyAllocationGroup( hpiHostApi->allocations );
+ }
+
+ PaUtil_FreeMemory( hpiHostApi );
+ }
+error:
+ return;
+}
+
+
+/** Converts PortAudio sample format to equivalent HPI format.
+
+ @param paFormat PortAudio sample format
+
+ @return HPI sample format
+ */
+static uint16_t PaAsiHpi_PaToHpiFormat( PaSampleFormat paFormat )
+{
+ /* Ignore interleaving flag */
+ switch( paFormat & ~paNonInterleaved )
+ {
+ case paFloat32:
+ return HPI_FORMAT_PCM32_FLOAT;
+
+ case paInt32:
+ return HPI_FORMAT_PCM32_SIGNED;
+
+ case paInt24:
+ return HPI_FORMAT_PCM24_SIGNED;
+
+ case paInt16:
+ return HPI_FORMAT_PCM16_SIGNED;
+
+ case paUInt8:
+ return HPI_FORMAT_PCM8_UNSIGNED;
+
+ /* Default is 16-bit signed */
+ case paInt8:
+ default:
+ return HPI_FORMAT_PCM16_SIGNED;
+ }
+}
+
+
+/** Converts HPI sample format to equivalent PortAudio format.
+
+ @param paFormat HPI sample format
+
+ @return PortAudio sample format
+ */
+static PaSampleFormat PaAsiHpi_HpiToPaFormat( uint16_t hpiFormat )
+{
+ switch( hpiFormat )
+ {
+ case HPI_FORMAT_PCM32_FLOAT:
+ return paFloat32;
+
+ case HPI_FORMAT_PCM32_SIGNED:
+ return paInt32;
+
+ case HPI_FORMAT_PCM24_SIGNED:
+ return paInt24;
+
+ case HPI_FORMAT_PCM16_SIGNED:
+ return paInt16;
+
+ case HPI_FORMAT_PCM8_UNSIGNED:
+ return paUInt8;
+
+ /* Default is custom format (e.g. for HPI MP3 format) */
+ default:
+ return paCustomFormat;
+ }
+}
+
+
+/** Creates HPI format struct based on PortAudio parameters.
+ This also does some checks to see whether the desired format is valid, and whether
+ the device allows it. This only checks the format of one half (input or output) of the
+ PortAudio stream.
+
+ @param hostApi Pointer to host API struct
+
+ @param parameters Pointer to stream parameter struct
+
+ @param sampleRate Desired sample rate
+
+ @param hpiDevice Pointer to HPI device struct
+
+ @param hpiFormat Resulting HPI format returned here
+
+ @return PortAudio error code (typically indicating a problem with stream format)
+ */
+static PaError PaAsiHpi_CreateFormat( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *parameters, double sampleRate,
+ PaAsiHpiDeviceInfo **hpiDevice, struct hpi_format *hpiFormat )
+{
+ int maxChannelCount = 0;
+ PaSampleFormat hostSampleFormat = 0;
+ hpi_err_t hpiError = 0;
+
+ /* Unless alternate device specification is supported, reject the use of
+ paUseHostApiSpecificDeviceSpecification */
+ if( parameters->device == paUseHostApiSpecificDeviceSpecification )
+ return paInvalidDevice;
+ else
+ {
+ assert( parameters->device < hostApi->info.deviceCount );
+ *hpiDevice = (PaAsiHpiDeviceInfo*) hostApi->deviceInfos[ parameters->device ];
+ }
+
+ /* Validate streamInfo - this implementation doesn't use custom stream info */
+ if( parameters->hostApiSpecificStreamInfo )
+ return paIncompatibleHostApiSpecificStreamInfo;
+
+ /* Check that device can support channel count */
+ if( (*hpiDevice)->streamIsOutput )
+ {
+ maxChannelCount = (*hpiDevice)->baseDeviceInfo.maxOutputChannels;
+ }
+ else
+ {
+ maxChannelCount = (*hpiDevice)->baseDeviceInfo.maxInputChannels;
+ }
+ if( (maxChannelCount == 0) || (parameters->channelCount > maxChannelCount) )
+ return paInvalidChannelCount;
+
+ /* All standard sample formats are supported by the buffer adapter,
+ and this implementation doesn't support any custom sample formats */
+ if( parameters->sampleFormat & paCustomFormat )
+ return paSampleFormatNotSupported;
+
+ /* Switch to closest HPI native format */
+ hostSampleFormat = PaUtil_SelectClosestAvailableFormat(PA_ASIHPI_AVAILABLE_FORMATS_,
+ parameters->sampleFormat );
+ /* Setup format + info objects */
+ hpiError = HPI_FormatCreate( hpiFormat, (uint16_t)parameters->channelCount,
+ PaAsiHpi_PaToHpiFormat( hostSampleFormat ),
+ (uint32_t)sampleRate, 0, 0 );
+ if( hpiError )
+ {
+ PA_ASIHPI_REPORT_ERROR_( hpiError );
+ switch( hpiError )
+ {
+ case HPI_ERROR_INVALID_FORMAT:
+ return paSampleFormatNotSupported;
+
+ case HPI_ERROR_INVALID_SAMPLERATE:
+ case HPI_ERROR_INCOMPATIBLE_SAMPLERATE:
+ return paInvalidSampleRate;
+
+ case HPI_ERROR_INVALID_CHANNELS:
+ return paInvalidChannelCount;
+ }
+ }
+
+ return paNoError;
+}
+
+
+/** Open HPI input stream with given format.
+ This attempts to open HPI input stream with desired format. If the format is not supported
+ or the device is unavailable, the stream is closed and a PortAudio error code is returned.
+
+ @param hostApi Pointer to host API struct
+
+ @param hpiDevice Pointer to HPI device struct
+
+ @param hpiFormat Pointer to HPI format struct
+
+ @return PortAudio error code (typically indicating a problem with stream format or device)
+*/
+static PaError PaAsiHpi_OpenInput( struct PaUtilHostApiRepresentation *hostApi,
+ const PaAsiHpiDeviceInfo *hpiDevice, const struct hpi_format *hpiFormat,
+ hpi_handle_t *hpiStream )
+{
+ PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+ PaError result = paNoError;
+ hpi_err_t hpiError = 0;
+
+ /* Catch misplaced output devices, as they typically have 0 input channels */
+ PA_UNLESS_( !hpiDevice->streamIsOutput, paInvalidChannelCount );
+ /* Try to open input stream */
+ PA_ASIHPI_UNLESS_( HPI_InStreamOpen( NULL, hpiDevice->adapterIndex,
+ hpiDevice->streamIndex, hpiStream ), paDeviceUnavailable );
+ /* Set input format (checking it in the process) */
+ /* Could also use HPI_InStreamQueryFormat, but this economizes the process */
+ hpiError = HPI_InStreamSetFormat( NULL, *hpiStream, (struct hpi_format*)hpiFormat );
+ if( hpiError )
+ {
+ PA_ASIHPI_REPORT_ERROR_( hpiError );
+ PA_ASIHPI_UNLESS_( HPI_InStreamClose( NULL, *hpiStream ), paNoError );
+ switch( hpiError )
+ {
+ case HPI_ERROR_INVALID_FORMAT:
+ return paSampleFormatNotSupported;
+
+ case HPI_ERROR_INVALID_SAMPLERATE:
+ case HPI_ERROR_INCOMPATIBLE_SAMPLERATE:
+ return paInvalidSampleRate;
+
+ case HPI_ERROR_INVALID_CHANNELS:
+ return paInvalidChannelCount;
+
+ default:
+ /* In case anything else went wrong */
+ return paInvalidDevice;
+ }
+ }
+
+error:
+ return result;
+}
+
+
+/** Open HPI output stream with given format.
+ This attempts to open HPI output stream with desired format. If the format is not supported
+ or the device is unavailable, the stream is closed and a PortAudio error code is returned.
+
+ @param hostApi Pointer to host API struct
+
+ @param hpiDevice Pointer to HPI device struct
+
+ @param hpiFormat Pointer to HPI format struct
+
+ @return PortAudio error code (typically indicating a problem with stream format or device)
+*/
+static PaError PaAsiHpi_OpenOutput( struct PaUtilHostApiRepresentation *hostApi,
+ const PaAsiHpiDeviceInfo *hpiDevice, const struct hpi_format *hpiFormat,
+ hpi_handle_t *hpiStream )
+{
+ PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+ PaError result = paNoError;
+ hpi_err_t hpiError = 0;
+
+ /* Catch misplaced input devices, as they typically have 0 output channels */
+ PA_UNLESS_( hpiDevice->streamIsOutput, paInvalidChannelCount );
+ /* Try to open output stream */
+ PA_ASIHPI_UNLESS_( HPI_OutStreamOpen( NULL, hpiDevice->adapterIndex,
+ hpiDevice->streamIndex, hpiStream ), paDeviceUnavailable );
+
+ /* Check output format (format is set on first write to output stream) */
+ hpiError = HPI_OutStreamQueryFormat( NULL, *hpiStream, (struct hpi_format*)hpiFormat );
+ if( hpiError )
+ {
+ PA_ASIHPI_REPORT_ERROR_( hpiError );
+ PA_ASIHPI_UNLESS_( HPI_OutStreamClose( NULL, *hpiStream ), paNoError );
+ switch( hpiError )
+ {
+ case HPI_ERROR_INVALID_FORMAT:
+ return paSampleFormatNotSupported;
+
+ case HPI_ERROR_INVALID_SAMPLERATE:
+ case HPI_ERROR_INCOMPATIBLE_SAMPLERATE:
+ return paInvalidSampleRate;
+
+ case HPI_ERROR_INVALID_CHANNELS:
+ return paInvalidChannelCount;
+
+ default:
+ /* In case anything else went wrong */
+ return paInvalidDevice;
+ }
+ }
+
+error:
+ return result;
+}
+
+
+/** Checks whether the desired stream formats and devices are supported
+ (for both input and output).
+ This is done by actually opening the appropriate HPI streams and closing them again.
+
+ @param hostApi Pointer to host API struct
+
+ @param inputParameters Pointer to stream parameter struct for input side of stream
+
+ @param outputParameters Pointer to stream parameter struct for output side of stream
+
+ @param sampleRate Desired sample rate
+
+ @return PortAudio error code (paFormatIsSupported on success)
+ */
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate )
+{
+ PaError result = paFormatIsSupported;
+ PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+ PaAsiHpiDeviceInfo *hpiDevice = NULL;
+ struct hpi_format hpiFormat;
+
+ /* Input stream */
+ if( inputParameters )
+ {
+ hpi_handle_t hpiStream;
+ PA_DEBUG(( "%s: Checking input params: dev=%d, sr=%d, chans=%d, fmt=%d\n",
+ __FUNCTION__, inputParameters->device, (int)sampleRate,
+ inputParameters->channelCount, inputParameters->sampleFormat ));
+ /* Create and validate format */
+ PA_ENSURE_( PaAsiHpi_CreateFormat( hostApi, inputParameters, sampleRate,
+ &hpiDevice, &hpiFormat ) );
+ /* Open stream to further check format */
+ PA_ENSURE_( PaAsiHpi_OpenInput( hostApi, hpiDevice, &hpiFormat, &hpiStream ) );
+ /* Close stream again */
+ PA_ASIHPI_UNLESS_( HPI_InStreamClose( NULL, hpiStream ), paNoError );
+ }
+
+ /* Output stream */
+ if( outputParameters )
+ {
+ hpi_handle_t hpiStream;
+ PA_DEBUG(( "%s: Checking output params: dev=%d, sr=%d, chans=%d, fmt=%d\n",
+ __FUNCTION__, outputParameters->device, (int)sampleRate,
+ outputParameters->channelCount, outputParameters->sampleFormat ));
+ /* Create and validate format */
+ PA_ENSURE_( PaAsiHpi_CreateFormat( hostApi, outputParameters, sampleRate,
+ &hpiDevice, &hpiFormat ) );
+ /* Open stream to further check format */
+ PA_ENSURE_( PaAsiHpi_OpenOutput( hostApi, hpiDevice, &hpiFormat, &hpiStream ) );
+ /* Close stream again */
+ PA_ASIHPI_UNLESS_( HPI_OutStreamClose( NULL, hpiStream ), paNoError );
+ }
+
+error:
+ return result;
+}
+
+/* ---------------------------- Stream Interface ---------------------------- */
+
+/** Obtain HPI stream information.
+ This obtains info such as stream state and available data/space in buffers. It also
+ estimates whether an underflow or overflow occurred.
+
+ @param streamComp Pointer to stream component (input or output) to query
+
+ @param info Pointer to stream info struct that will contain result
+
+ @return PortAudio error code (either paNoError, paDeviceUnavailable or paUnanticipatedHostError)
+ */
+static PaError PaAsiHpi_GetStreamInfo( PaAsiHpiStreamComponent *streamComp, PaAsiHpiStreamInfo *info )
+{
+ PaError result = paDeviceUnavailable;
+ uint16_t state;
+ uint32_t bufferSize, dataSize, frameCounter, auxDataSize, threshold;
+ uint32_t hwBufferSize, hwDataSize;
+
+ assert( streamComp );
+ assert( info );
+
+ /* First blank the stream info struct, in case something goes wrong below.
+ This saves the caller from initializing the struct. */
+ info->state = 0;
+ info->bufferSize = 0;
+ info->dataSize = 0;
+ info->frameCounter = 0;
+ info->auxDataSize = 0;
+ info->totalBufferedData = 0;
+ info->availableFrames = 0;
+ info->underflow = 0;
+ info->overflow = 0;
+
+ if( streamComp->hpiDevice && streamComp->hpiStream )
+ {
+ /* Obtain detailed stream info (either input or output) */
+ if( streamComp->hpiDevice->streamIsOutput )
+ {
+ PA_ASIHPI_UNLESS_( HPI_OutStreamGetInfoEx( NULL,
+ streamComp->hpiStream,
+ &state, &bufferSize, &dataSize, &frameCounter,
+ &auxDataSize ), paUnanticipatedHostError );
+ }
+ else
+ {
+ PA_ASIHPI_UNLESS_( HPI_InStreamGetInfoEx( NULL,
+ streamComp->hpiStream,
+ &state, &bufferSize, &dataSize, &frameCounter,
+ &auxDataSize ), paUnanticipatedHostError );
+ }
+ /* Load stream info */
+ info->state = state;
+ info->bufferSize = bufferSize;
+ info->dataSize = dataSize;
+ info->frameCounter = frameCounter;
+ info->auxDataSize = auxDataSize;
+ /* Determine total buffered data */
+ info->totalBufferedData = dataSize;
+ if( streamComp->hostBufferSize > 0 )
+ info->totalBufferedData += auxDataSize;
+ info->totalBufferedData /= streamComp->bytesPerFrame;
+ /* Determine immediately available frames */
+ info->availableFrames = streamComp->hpiDevice->streamIsOutput ?
+ bufferSize - dataSize : dataSize;
+ info->availableFrames /= streamComp->bytesPerFrame;
+ /* Minimum space/data required in buffers */
+ threshold = PA_MIN( streamComp->tempBufferSize,
+ streamComp->bytesPerFrame * PA_ASIHPI_MIN_FRAMES_ );
+ /* Obtain hardware buffer stats first, to simplify things */
+ hwBufferSize = streamComp->hardwareBufferSize;
+ hwDataSize = streamComp->hostBufferSize > 0 ? auxDataSize : dataSize;
+ /* Underflow is a bit tricky */
+ info->underflow = streamComp->hpiDevice->streamIsOutput ?
+ /* Stream seems to start in drained state sometimes, so ignore initial underflow */
+ (frameCounter > 0) && ( (state == HPI_STATE_DRAINED) || (hwDataSize == 0) ) :
+ /* Input streams check the first-level (host) buffer for underflow */
+ (state != HPI_STATE_STOPPED) && (dataSize < threshold);
+ /* Check for overflow in second-level (hardware) buffer for both input and output */
+ info->overflow = (state != HPI_STATE_STOPPED) && (hwBufferSize - hwDataSize < threshold);
+
+ return paNoError;
+ }
+
+error:
+ return result;
+}
+
+
+/** Display stream component information for debugging purposes.
+
+ @param streamComp Pointer to stream component (input or output) to query
+
+ @param stream Pointer to stream struct which contains the component above
+ */
+static void PaAsiHpi_StreamComponentDump( PaAsiHpiStreamComponent *streamComp,
+ PaAsiHpiStream *stream )
+{
+ PaAsiHpiStreamInfo streamInfo;
+
+ assert( streamComp );
+ assert( stream );
+
+ /* Name of soundcard/device used by component */
+ PA_DEBUG(( "device: %s\n", streamComp->hpiDevice->baseDeviceInfo.name ));
+ /* Unfortunately some overlap between input and output here */
+ if( streamComp->hpiDevice->streamIsOutput )
+ {
+ /* Settings on the user side (as experienced by user callback) */
+ PA_DEBUG(( "user: %d-bit, %d ",
+ 8*stream->bufferProcessor.bytesPerUserOutputSample,
+ stream->bufferProcessor.outputChannelCount));
+ if( stream->bufferProcessor.userOutputIsInterleaved )
+ {
+ PA_DEBUG(( "interleaved channels, " ));
+ }
+ else
+ {
+ PA_DEBUG(( "non-interleaved channels, " ));
+ }
+ PA_DEBUG(( "%d frames/buffer, latency = %5.1f ms\n",
+ stream->bufferProcessor.framesPerUserBuffer,
+ 1000*stream->baseStreamRep.streamInfo.outputLatency ));
+ /* Settings on the host side (internal to PortAudio host API) */
+ PA_DEBUG(( "host: %d-bit, %d interleaved channels, %d frames/buffer ",
+ 8*stream->bufferProcessor.bytesPerHostOutputSample,
+ stream->bufferProcessor.outputChannelCount,
+ stream->bufferProcessor.framesPerHostBuffer ));
+ }
+ else
+ {
+ /* Settings on the user side (as experienced by user callback) */
+ PA_DEBUG(( "user: %d-bit, %d ",
+ 8*stream->bufferProcessor.bytesPerUserInputSample,
+ stream->bufferProcessor.inputChannelCount));
+ if( stream->bufferProcessor.userInputIsInterleaved )
+ {
+ PA_DEBUG(( "interleaved channels, " ));
+ }
+ else
+ {
+ PA_DEBUG(( "non-interleaved channels, " ));
+ }
+ PA_DEBUG(( "%d frames/buffer, latency = %5.1f ms\n",
+ stream->bufferProcessor.framesPerUserBuffer,
+ 1000*stream->baseStreamRep.streamInfo.inputLatency ));
+ /* Settings on the host side (internal to PortAudio host API) */
+ PA_DEBUG(( "host: %d-bit, %d interleaved channels, %d frames/buffer ",
+ 8*stream->bufferProcessor.bytesPerHostInputSample,
+ stream->bufferProcessor.inputChannelCount,
+ stream->bufferProcessor.framesPerHostBuffer ));
+ }
+ switch( stream->bufferProcessor.hostBufferSizeMode )
+ {
+ case paUtilFixedHostBufferSize:
+ PA_DEBUG(( "[fixed] " ));
+ break;
+ case paUtilBoundedHostBufferSize:
+ PA_DEBUG(( "[bounded] " ));
+ break;
+ case paUtilUnknownHostBufferSize:
+ PA_DEBUG(( "[unknown] " ));
+ break;
+ case paUtilVariableHostBufferSizePartialUsageAllowed:
+ PA_DEBUG(( "[variable] " ));
+ break;
+ }
+ PA_DEBUG(( "(%d max)\n", streamComp->tempBufferSize / streamComp->bytesPerFrame ));
+ /* HPI hardware settings */
+ PA_DEBUG(( "HPI: adapter %d stream %d, %d-bit, %d-channel, %d Hz\n",
+ streamComp->hpiDevice->adapterIndex, streamComp->hpiDevice->streamIndex,
+ 8 * streamComp->bytesPerFrame / streamComp->hpiFormat.wChannels,
+ streamComp->hpiFormat.wChannels,
+ streamComp->hpiFormat.dwSampleRate ));
+ /* Stream state and buffer levels */
+ PA_DEBUG(( "HPI: " ));
+ PaAsiHpi_GetStreamInfo( streamComp, &streamInfo );
+ switch( streamInfo.state )
+ {
+ case HPI_STATE_STOPPED:
+ PA_DEBUG(( "[STOPPED] " ));
+ break;
+ case HPI_STATE_PLAYING:
+ PA_DEBUG(( "[PLAYING] " ));
+ break;
+ case HPI_STATE_RECORDING:
+ PA_DEBUG(( "[RECORDING] " ));
+ break;
+ case HPI_STATE_DRAINED:
+ PA_DEBUG(( "[DRAINED] " ));
+ break;
+ default:
+ PA_DEBUG(( "[unknown state] " ));
+ break;
+ }
+ if( streamComp->hostBufferSize )
+ {
+ PA_DEBUG(( "host = %d/%d B, ", streamInfo.dataSize, streamComp->hostBufferSize ));
+ PA_DEBUG(( "hw = %d/%d (%d) B, ", streamInfo.auxDataSize,
+ streamComp->hardwareBufferSize, streamComp->outputBufferCap ));
+ }
+ else
+ {
+ PA_DEBUG(( "hw = %d/%d B, ", streamInfo.dataSize, streamComp->hardwareBufferSize ));
+ }
+ PA_DEBUG(( "count = %d", streamInfo.frameCounter ));
+ if( streamInfo.overflow )
+ {
+ PA_DEBUG(( " [overflow]" ));
+ }
+ else if( streamInfo.underflow )
+ {
+ PA_DEBUG(( " [underflow]" ));
+ }
+ PA_DEBUG(( "\n" ));
+}
+
+
+/** Display stream information for debugging purposes.
+
+ @param stream Pointer to stream to query
+ */
+static void PaAsiHpi_StreamDump( PaAsiHpiStream *stream )
+{
+ assert( stream );
+
+ PA_DEBUG(( "\n------------------------- STREAM INFO FOR %p ---------------------------\n", stream ));
+ /* General stream info (input+output) */
+ if( stream->baseStreamRep.streamCallback )
+ {
+ PA_DEBUG(( "[callback] " ));
+ }
+ else
+ {
+ PA_DEBUG(( "[blocking] " ));
+ }
+ PA_DEBUG(( "sr=%d Hz, poll=%d ms, max %d frames/buf ",
+ (int)stream->baseStreamRep.streamInfo.sampleRate,
+ stream->pollingInterval, stream->maxFramesPerHostBuffer ));
+ switch( stream->state )
+ {
+ case paAsiHpiStoppedState:
+ PA_DEBUG(( "[stopped]\n" ));
+ break;
+ case paAsiHpiActiveState:
+ PA_DEBUG(( "[active]\n" ));
+ break;
+ case paAsiHpiCallbackFinishedState:
+ PA_DEBUG(( "[cb fin]\n" ));
+ break;
+ default:
+ PA_DEBUG(( "[unknown state]\n" ));
+ break;
+ }
+ if( stream->callbackMode )
+ {
+ PA_DEBUG(( "cb info: thread=%p, cbAbort=%d, cbFinished=%d\n",
+ stream->thread.thread, stream->callbackAbort, stream->callbackFinished ));
+ }
+
+ PA_DEBUG(( "----------------------------------- Input ------------------------------------\n" ));
+ if( stream->input )
+ {
+ PaAsiHpi_StreamComponentDump( stream->input, stream );
+ }
+ else
+ {
+ PA_DEBUG(( "*none*\n" ));
+ }
+
+ PA_DEBUG(( "----------------------------------- Output ------------------------------------\n" ));
+ if( stream->output )
+ {
+ PaAsiHpi_StreamComponentDump( stream->output, stream );
+ }
+ else
+ {
+ PA_DEBUG(( "*none*\n" ));
+ }
+ PA_DEBUG(( "-------------------------------------------------------------------------------\n\n" ));
+
+}
+
+
+/** Determine buffer sizes and allocate appropriate stream buffers.
+ This attempts to allocate a BBM (host) buffer for the HPI stream component (either input
+ or output, as both have similar buffer needs). Not all AudioScience adapters support BBM,
+ in which case the hardware buffer has to suffice. The size of the HPI host buffer is chosen
+ as a multiple of framesPerPaHostBuffer, and also influenced by the suggested latency and the
+ estimated minimum polling interval. The HPI host and hardware buffer sizes are stored, and an
+ appropriate cap for the hardware buffer is also calculated. Finally, the temporary stream
+ buffer which serves as the PortAudio host buffer for this implementation is allocated.
+ This buffer contains an integer number of user buffers, to simplify buffer adaption in the
+ buffer processor. The function returns paBufferTooBig if the HPI interface cannot allocate
+ an HPI host buffer of the desired size.
+
+ @param streamComp Pointer to stream component struct
+
+ @param pollingInterval Polling interval for stream, in milliseconds
+
+ @param framesPerPaHostBuffer Size of PortAudio host buffer, in frames
+
+ @param suggestedLatency Suggested latency for stream component, in seconds
+
+ @return PortAudio error code (possibly paBufferTooBig or paInsufficientMemory)
+ */
+static PaError PaAsiHpi_SetupBuffers( PaAsiHpiStreamComponent *streamComp, uint32_t pollingInterval,
+ unsigned long framesPerPaHostBuffer, PaTime suggestedLatency )
+{
+ PaError result = paNoError;
+ PaAsiHpiStreamInfo streamInfo;
+ unsigned long hpiBufferSize = 0, paHostBufferSize = 0;
+
+ assert( streamComp );
+ assert( streamComp->hpiDevice );
+
+ /* Obtain size of hardware buffer of HPI stream, since we will be activating BBM shortly
+ and afterwards the buffer size will refer to the BBM (host-side) buffer.
+ This is necessary to enable reliable detection of xruns. */
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( streamComp, &streamInfo ) );
+ streamComp->hardwareBufferSize = streamInfo.bufferSize;
+ hpiBufferSize = streamInfo.bufferSize;
+
+ /* Check if BBM (background bus mastering) is to be enabled */
+ if( PA_ASIHPI_USE_BBM_ )
+ {
+ uint32_t bbmBufferSize = 0, preLatencyBufferSize = 0;
+ hpi_err_t hpiError = 0;
+ PaTime pollingOverhead;
+
+ /* Check overhead of Pa_Sleep() call (minimum sleep duration in ms -> OS dependent) */
+ pollingOverhead = PaUtil_GetTime();
+ Pa_Sleep( 0 );
+ pollingOverhead = 1000*(PaUtil_GetTime() - pollingOverhead);
+ PA_DEBUG(( "polling overhead = %f ms (length of 0-second sleep)\n", pollingOverhead ));
+ /* Obtain minimum recommended size for host buffer (in bytes) */
+ PA_ASIHPI_UNLESS_( HPI_StreamEstimateBufferSize( &streamComp->hpiFormat,
+ pollingInterval + (uint32_t)ceil( pollingOverhead ),
+ &bbmBufferSize ), paUnanticipatedHostError );
+ /* BBM places more stringent requirements on buffer size (see description */
+ /* of HPI_StreamEstimateBufferSize in HPI API document) */
+ bbmBufferSize *= 3;
+ /* Make sure the BBM buffer contains multiple PA host buffers */
+ if( bbmBufferSize < 3 * streamComp->bytesPerFrame * framesPerPaHostBuffer )
+ bbmBufferSize = 3 * streamComp->bytesPerFrame * framesPerPaHostBuffer;
+ /* Try to honor latency suggested by user by growing buffer (no decrease possible) */
+ if( suggestedLatency > 0.0 )
+ {
+ PaTime bufferDuration = ((PaTime)bbmBufferSize) / streamComp->bytesPerFrame
+ / streamComp->hpiFormat.dwSampleRate;
+ /* Don't decrease buffer */
+ if( bufferDuration < suggestedLatency )
+ {
+ /* Save old buffer size, to be retried if new size proves too big */
+ preLatencyBufferSize = bbmBufferSize;
+ bbmBufferSize = (uint32_t)ceil( suggestedLatency * streamComp->bytesPerFrame
+ * streamComp->hpiFormat.dwSampleRate );
+ }
+ }
+ /* Choose closest memory block boundary (HPI API document states that
+ "a buffer size of Nx4096 - 20 makes the best use of memory"
+ (under the entry for HPI_StreamEstimateBufferSize)) */
+ bbmBufferSize = ((uint32_t)ceil((bbmBufferSize + 20)/4096.0))*4096 - 20;
+ streamComp->hostBufferSize = bbmBufferSize;
+ /* Allocate BBM host buffer (this enables bus mastering transfers in background) */
+ if( streamComp->hpiDevice->streamIsOutput )
+ hpiError = HPI_OutStreamHostBufferAllocate( NULL,
+ streamComp->hpiStream,
+ bbmBufferSize );
+ else
+ hpiError = HPI_InStreamHostBufferAllocate( NULL,
+ streamComp->hpiStream,
+ bbmBufferSize );
+ if( hpiError )
+ {
+ /* Indicate that BBM is disabled */
+ streamComp->hostBufferSize = 0;
+ /* Retry with smaller buffer size (transfers will still work, but not via BBM) */
+ if( hpiError == HPI_ERROR_INVALID_DATASIZE )
+ {
+ /* Retry BBM allocation with smaller size if requested latency proved too big */
+ if( preLatencyBufferSize > 0 )
+ {
+ PA_DEBUG(( "Retrying BBM allocation with smaller size (%d vs. %d bytes)\n",
+ preLatencyBufferSize, bbmBufferSize ));
+ bbmBufferSize = preLatencyBufferSize;
+ if( streamComp->hpiDevice->streamIsOutput )
+ hpiError = HPI_OutStreamHostBufferAllocate( NULL,
+ streamComp->hpiStream,
+ bbmBufferSize );
+ else
+ hpiError = HPI_InStreamHostBufferAllocate( NULL,
+ streamComp->hpiStream,
+ bbmBufferSize );
+ /* Another round of error checking */
+ if( hpiError )
+ {
+ PA_ASIHPI_REPORT_ERROR_( hpiError );
+ /* No escapes this time */
+ if( hpiError == HPI_ERROR_INVALID_DATASIZE )
+ {
+ result = paBufferTooBig;
+ goto error;
+ }
+ else if( hpiError != HPI_ERROR_INVALID_OPERATION )
+ {
+ result = paUnanticipatedHostError;
+ goto error;
+ }
+ }
+ else
+ {
+ streamComp->hostBufferSize = bbmBufferSize;
+ hpiBufferSize = bbmBufferSize;
+ }
+ }
+ else
+ {
+ result = paBufferTooBig;
+ goto error;
+ }
+ }
+ /* If BBM not supported, foreground transfers will be used, but not a show-stopper */
+ /* Anything else is an error */
+ else if (( hpiError != HPI_ERROR_INVALID_OPERATION ) &&
+ ( hpiError != HPI_ERROR_INVALID_FUNC ))
+ {
+ PA_ASIHPI_REPORT_ERROR_( hpiError );
+ result = paUnanticipatedHostError;
+ goto error;
+ }
+ }
+ else
+ {
+ hpiBufferSize = bbmBufferSize;
+ }
+ }
+
+ /* Final check of buffer size */
+ paHostBufferSize = streamComp->bytesPerFrame * framesPerPaHostBuffer;
+ if( hpiBufferSize < 3*paHostBufferSize )
+ {
+ result = paBufferTooBig;
+ goto error;
+ }
+ /* Set cap on output buffer size, based on latency suggestions */
+ if( streamComp->hpiDevice->streamIsOutput )
+ {
+ PaTime latency = suggestedLatency > 0.0 ? suggestedLatency :
+ streamComp->hpiDevice->baseDeviceInfo.defaultHighOutputLatency;
+ streamComp->outputBufferCap =
+ (uint32_t)ceil( latency * streamComp->bytesPerFrame * streamComp->hpiFormat.dwSampleRate );
+ /* The cap should not be too small, to prevent underflow */
+ if( streamComp->outputBufferCap < 4*paHostBufferSize )
+ streamComp->outputBufferCap = 4*paHostBufferSize;
+ }
+ else
+ {
+ streamComp->outputBufferCap = 0;
+ }
+ /* Temp buffer size should be multiple of PA host buffer size (or 1x, if using fixed blocks) */
+ streamComp->tempBufferSize = paHostBufferSize;
+ /* Allocate temp buffer */
+ PA_UNLESS_( streamComp->tempBuffer = (uint8_t *)PaUtil_AllocateMemory( streamComp->tempBufferSize ),
+ paInsufficientMemory );
+error:
+ return result;
+}
+
+
+/** Opens PortAudio stream.
+ This determines a suitable value for framesPerBuffer, if the user didn't specify it,
+ based on the suggested latency. It then opens each requested stream direction with the
+ appropriate stream format, and allocates the required stream buffers. It sets up the
+ various PortAudio structures dealing with streams, and estimates the stream latency.
+
+ See pa_hostapi.h for a list of validity guarantees made about OpenStream parameters.
+
+ @param hostApi Pointer to host API struct
+
+ @param s List of open streams, where successfully opened stream will go
+
+ @param inputParameters Pointer to stream parameter struct for input side of stream
+
+ @param outputParameters Pointer to stream parameter struct for output side of stream
+
+ @param sampleRate Desired sample rate
+
+ @param framesPerBuffer Desired number of frames per buffer passed to user callback
+ (or chunk size for blocking stream)
+
+ @param streamFlags Stream flags
+
+ @param streamCallback Pointer to user callback function (zero for blocking interface)
+
+ @param userData Pointer to user data that will be passed to callback function along with data
+
+ @return PortAudio error code
+*/
+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;
+ PaAsiHpiHostApiRepresentation *hpiHostApi = (PaAsiHpiHostApiRepresentation*)hostApi;
+ PaAsiHpiStream *stream = NULL;
+ unsigned long framesPerHostBuffer = framesPerBuffer;
+ int inputChannelCount = 0, outputChannelCount = 0;
+ PaSampleFormat inputSampleFormat = 0, outputSampleFormat = 0;
+ PaSampleFormat hostInputSampleFormat = 0, hostOutputSampleFormat = 0;
+ PaTime maxSuggestedLatency = 0.0;
+
+ /* Validate platform-specific flags -> none expected for HPI */
+ if( (streamFlags & paPlatformSpecificFlags) != 0 )
+ return paInvalidFlag; /* unexpected platform-specific flag */
+
+ /* Create blank stream structure */
+ PA_UNLESS_( stream = (PaAsiHpiStream *)PaUtil_AllocateMemory( sizeof(PaAsiHpiStream) ),
+ paInsufficientMemory );
+ memset( stream, 0, sizeof(PaAsiHpiStream) );
+
+ /* If the number of frames per buffer is unspecified, we have to come up with one. */
+ if( framesPerHostBuffer == paFramesPerBufferUnspecified )
+ {
+ if( inputParameters )
+ maxSuggestedLatency = inputParameters->suggestedLatency;
+ if( outputParameters && (outputParameters->suggestedLatency > maxSuggestedLatency) )
+ maxSuggestedLatency = outputParameters->suggestedLatency;
+ /* Use suggested latency if available */
+ if( maxSuggestedLatency > 0.0 )
+ framesPerHostBuffer = (unsigned long)ceil( maxSuggestedLatency * sampleRate );
+ else
+ /* AudioScience cards like BIG buffers by default */
+ framesPerHostBuffer = 4096;
+ }
+ /* Lower bounds on host buffer size, due to polling and HPI constraints */
+ if( 1000.0*framesPerHostBuffer/sampleRate < PA_ASIHPI_MIN_POLLING_INTERVAL_ )
+ framesPerHostBuffer = (unsigned long)ceil( sampleRate * PA_ASIHPI_MIN_POLLING_INTERVAL_ / 1000.0 );
+ /* if( framesPerHostBuffer < PA_ASIHPI_MIN_FRAMES_ )
+ framesPerHostBuffer = PA_ASIHPI_MIN_FRAMES_; */
+ /* Efficient if host buffer size is integer multiple of user buffer size */
+ if( framesPerBuffer > 0 )
+ framesPerHostBuffer = (unsigned long)ceil( (double)framesPerHostBuffer / framesPerBuffer ) * framesPerBuffer;
+ /* Buffer should always be a multiple of 4 bytes to facilitate 32-bit PCI transfers.
+ By keeping the frames a multiple of 4, this is ensured even for 8-bit mono sound. */
+ framesPerHostBuffer = (framesPerHostBuffer / 4) * 4;
+ /* Polling is based on time length (in milliseconds) of user-requested block size */
+ stream->pollingInterval = (uint32_t)ceil( 1000.0*framesPerHostBuffer/sampleRate );
+ assert( framesPerHostBuffer > 0 );
+
+ /* Open underlying streams, check formats and allocate buffers */
+ if( inputParameters )
+ {
+ /* Create blank stream component structure */
+ PA_UNLESS_( stream->input = (PaAsiHpiStreamComponent *)PaUtil_AllocateMemory( sizeof(PaAsiHpiStreamComponent) ),
+ paInsufficientMemory );
+ memset( stream->input, 0, sizeof(PaAsiHpiStreamComponent) );
+ /* Create/validate format */
+ PA_ENSURE_( PaAsiHpi_CreateFormat( hostApi, inputParameters, sampleRate,
+ &stream->input->hpiDevice, &stream->input->hpiFormat ) );
+ /* Open stream and set format */
+ PA_ENSURE_( PaAsiHpi_OpenInput( hostApi, stream->input->hpiDevice, &stream->input->hpiFormat,
+ &stream->input->hpiStream ) );
+ inputChannelCount = inputParameters->channelCount;
+ inputSampleFormat = inputParameters->sampleFormat;
+ hostInputSampleFormat = PaAsiHpi_HpiToPaFormat( stream->input->hpiFormat.wFormat );
+ stream->input->bytesPerFrame = inputChannelCount * Pa_GetSampleSize( hostInputSampleFormat );
+ assert( stream->input->bytesPerFrame > 0 );
+ /* Allocate host and temp buffers of appropriate size */
+ PA_ENSURE_( PaAsiHpi_SetupBuffers( stream->input, stream->pollingInterval,
+ framesPerHostBuffer, inputParameters->suggestedLatency ) );
+ }
+ if( outputParameters )
+ {
+ /* Create blank stream component structure */
+ PA_UNLESS_( stream->output = (PaAsiHpiStreamComponent *)PaUtil_AllocateMemory( sizeof(PaAsiHpiStreamComponent) ),
+ paInsufficientMemory );
+ memset( stream->output, 0, sizeof(PaAsiHpiStreamComponent) );
+ /* Create/validate format */
+ PA_ENSURE_( PaAsiHpi_CreateFormat( hostApi, outputParameters, sampleRate,
+ &stream->output->hpiDevice, &stream->output->hpiFormat ) );
+ /* Open stream and check format */
+ PA_ENSURE_( PaAsiHpi_OpenOutput( hostApi, stream->output->hpiDevice,
+ &stream->output->hpiFormat,
+ &stream->output->hpiStream ) );
+ outputChannelCount = outputParameters->channelCount;
+ outputSampleFormat = outputParameters->sampleFormat;
+ hostOutputSampleFormat = PaAsiHpi_HpiToPaFormat( stream->output->hpiFormat.wFormat );
+ stream->output->bytesPerFrame = outputChannelCount * Pa_GetSampleSize( hostOutputSampleFormat );
+ /* Allocate host and temp buffers of appropriate size */
+ PA_ENSURE_( PaAsiHpi_SetupBuffers( stream->output, stream->pollingInterval,
+ framesPerHostBuffer, outputParameters->suggestedLatency ) );
+ }
+
+ /* Determine maximum frames per host buffer (least common denominator of input/output) */
+ if( inputParameters && outputParameters )
+ {
+ stream->maxFramesPerHostBuffer = PA_MIN( stream->input->tempBufferSize / stream->input->bytesPerFrame,
+ stream->output->tempBufferSize / stream->output->bytesPerFrame );
+ }
+ else
+ {
+ stream->maxFramesPerHostBuffer = inputParameters ? stream->input->tempBufferSize / stream->input->bytesPerFrame
+ : stream->output->tempBufferSize / stream->output->bytesPerFrame;
+ }
+ assert( stream->maxFramesPerHostBuffer > 0 );
+ /* Initialize various other stream parameters */
+ stream->neverDropInput = streamFlags & paNeverDropInput;
+ stream->state = paAsiHpiStoppedState;
+
+ /* Initialize either callback or blocking interface */
+ if( streamCallback )
+ {
+ PaUtil_InitializeStreamRepresentation( &stream->baseStreamRep,
+ &hpiHostApi->callbackStreamInterface,
+ streamCallback, userData );
+ stream->callbackMode = 1;
+ }
+ else
+ {
+ PaUtil_InitializeStreamRepresentation( &stream->baseStreamRep,
+ &hpiHostApi->blockingStreamInterface,
+ streamCallback, userData );
+ /* Pre-allocate non-interleaved user buffer pointers for blocking interface */
+ PA_UNLESS_( stream->blockingUserBufferCopy =
+ PaUtil_AllocateMemory( sizeof(void *) * PA_MAX( inputChannelCount, outputChannelCount ) ),
+ paInsufficientMemory );
+ stream->callbackMode = 0;
+ }
+ PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
+
+ /* Following pa_linux_alsa's lead, we operate with fixed host buffer size by default, */
+ /* since other modes will invariably lead to block adaption (maybe Bounded better?) */
+ PA_ENSURE_( PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
+ inputChannelCount, inputSampleFormat, hostInputSampleFormat,
+ outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
+ sampleRate, streamFlags,
+ framesPerBuffer, framesPerHostBuffer, paUtilFixedHostBufferSize,
+ streamCallback, userData ) );
+
+ stream->baseStreamRep.streamInfo.structVersion = 1;
+ stream->baseStreamRep.streamInfo.sampleRate = sampleRate;
+ /* Determine input latency from buffer processor and buffer sizes */
+ if( stream->input )
+ {
+ PaTime bufferDuration = ( stream->input->hostBufferSize + stream->input->hardwareBufferSize )
+ / sampleRate / stream->input->bytesPerFrame;
+ stream->baseStreamRep.streamInfo.inputLatency =
+ bufferDuration +
+ ((PaTime)PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor ) -
+ stream->maxFramesPerHostBuffer) / sampleRate;
+ assert( stream->baseStreamRep.streamInfo.inputLatency > 0.0 );
+ }
+ /* Determine output latency from buffer processor and buffer sizes */
+ if( stream->output )
+ {
+ PaTime bufferDuration = ( stream->output->hostBufferSize + stream->output->hardwareBufferSize )
+ / sampleRate / stream->output->bytesPerFrame;
+ /* Take buffer size cap into account (see PaAsiHpi_WaitForFrames) */
+ if( !stream->input && (stream->output->outputBufferCap > 0) )
+ {
+ bufferDuration = PA_MIN( bufferDuration,
+ stream->output->outputBufferCap / sampleRate / stream->output->bytesPerFrame );
+ }
+ stream->baseStreamRep.streamInfo.outputLatency =
+ bufferDuration +
+ ((PaTime)PaUtil_GetBufferProcessorOutputLatencyFrames( &stream->bufferProcessor ) -
+ stream->maxFramesPerHostBuffer) / sampleRate;
+ assert( stream->baseStreamRep.streamInfo.outputLatency > 0.0 );
+ }
+
+ /* Report stream info, for debugging purposes */
+ PaAsiHpi_StreamDump( stream );
+
+ /* Save initialized stream to PA stream list */
+ *s = (PaStream*)stream;
+ return result;
+
+error:
+ CloseStream( (PaStream*)stream );
+ return result;
+}
+
+
+/** Close PortAudio stream.
+ When CloseStream() is called, the multi-api layer ensures that the stream has already
+ been stopped or aborted. This closes the underlying HPI streams and deallocates stream
+ buffers and structs.
+
+ @param s Pointer to PortAudio stream
+
+ @return PortAudio error code
+*/
+static PaError CloseStream( PaStream *s )
+{
+ PaError result = paNoError;
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+ /* If stream is already gone, all is well */
+ if( stream == NULL )
+ return paNoError;
+
+ /* Generic stream cleanup */
+ PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+ PaUtil_TerminateStreamRepresentation( &stream->baseStreamRep );
+
+ /* Implementation-specific details - close internal streams */
+ if( stream->input )
+ {
+ /* Close HPI stream (freeing BBM host buffer in the process, if used) */
+ if( stream->input->hpiStream )
+ {
+ PA_ASIHPI_UNLESS_( HPI_InStreamClose( NULL,
+ stream->input->hpiStream ), paUnanticipatedHostError );
+ }
+ /* Free temp buffer and stream component */
+ PaUtil_FreeMemory( stream->input->tempBuffer );
+ PaUtil_FreeMemory( stream->input );
+ }
+ if( stream->output )
+ {
+ /* Close HPI stream (freeing BBM host buffer in the process, if used) */
+ if( stream->output->hpiStream )
+ {
+ PA_ASIHPI_UNLESS_( HPI_OutStreamClose( NULL,
+ stream->output->hpiStream ), paUnanticipatedHostError );
+ }
+ /* Free temp buffer and stream component */
+ PaUtil_FreeMemory( stream->output->tempBuffer );
+ PaUtil_FreeMemory( stream->output );
+ }
+
+ PaUtil_FreeMemory( stream->blockingUserBufferCopy );
+ PaUtil_FreeMemory( stream );
+
+error:
+ return result;
+}
+
+
+/** Prime HPI output stream with silence.
+ This resets the output stream and uses PortAudio helper routines to fill the
+ temp buffer with silence. It then writes two host buffers to the stream. This is supposed
+ to be called before the stream is started. It has no effect on input-only streams.
+
+ @param stream Pointer to stream struct
+
+ @return PortAudio error code
+ */
+static PaError PaAsiHpi_PrimeOutputWithSilence( PaAsiHpiStream *stream )
+{
+ PaError result = paNoError;
+ PaAsiHpiStreamComponent *out;
+ PaUtilZeroer *zeroer;
+ PaSampleFormat outputFormat;
+ assert( stream );
+ out = stream->output;
+ /* Only continue if stream has output channels */
+ if( !out )
+ return result;
+ assert( out->tempBuffer );
+
+ /* Clear all existing data in hardware playback buffer */
+ PA_ASIHPI_UNLESS_( HPI_OutStreamReset( NULL,
+ out->hpiStream ), paUnanticipatedHostError );
+ /* Fill temp buffer with silence */
+ outputFormat = PaAsiHpi_HpiToPaFormat( out->hpiFormat.wFormat );
+ zeroer = PaUtil_SelectZeroer( outputFormat );
+ zeroer(out->tempBuffer, 1, out->tempBufferSize / Pa_GetSampleSize(outputFormat) );
+ /* Write temp buffer to hardware fifo twice, to get started */
+ PA_ASIHPI_UNLESS_( HPI_OutStreamWriteBuf( NULL, out->hpiStream,
+ out->tempBuffer, out->tempBufferSize, &out->hpiFormat),
+ paUnanticipatedHostError );
+ PA_ASIHPI_UNLESS_( HPI_OutStreamWriteBuf( NULL, out->hpiStream,
+ out->tempBuffer, out->tempBufferSize, &out->hpiFormat),
+ paUnanticipatedHostError );
+error:
+ return result;
+}
+
+
+/** Start HPI streams (both input + output).
+ This starts all HPI streams in the PortAudio stream. Output streams are first primed with
+ silence, if required. After this call the PA stream is in the Active state.
+
+ @todo Implement priming via the user callback
+
+ @param stream Pointer to stream struct
+
+ @param outputPrimed True if output is already primed (if false, silence will be loaded before starting)
+
+ @return PortAudio error code
+ */
+static PaError PaAsiHpi_StartStream( PaAsiHpiStream *stream, int outputPrimed )
+{
+ PaError result = paNoError;
+
+ if( stream->input )
+ {
+ PA_ASIHPI_UNLESS_( HPI_InStreamStart( NULL,
+ stream->input->hpiStream ), paUnanticipatedHostError );
+ }
+ if( stream->output )
+ {
+ if( !outputPrimed )
+ {
+ /* Buffer isn't primed, so load stream with silence */
+ PA_ENSURE_( PaAsiHpi_PrimeOutputWithSilence( stream ) );
+ }
+ PA_ASIHPI_UNLESS_( HPI_OutStreamStart( NULL,
+ stream->output->hpiStream ), paUnanticipatedHostError );
+ }
+ stream->state = paAsiHpiActiveState;
+ stream->callbackFinished = 0;
+
+ /* Report stream info for debugging purposes */
+ /* PaAsiHpi_StreamDump( stream ); */
+
+error:
+ return result;
+}
+
+
+/** Start PortAudio stream.
+ If the stream has a callback interface, this starts a helper thread to feed the user callback.
+ The thread will then take care of starting the HPI streams, and this function will block
+ until the streams actually start. In the case of a blocking interface, the HPI streams
+ are simply started.
+
+ @param s Pointer to PortAudio stream
+
+ @return PortAudio error code
+*/
+static PaError StartStream( PaStream *s )
+{
+ PaError result = paNoError;
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+ assert( stream );
+
+ /* Ready the processor */
+ PaUtil_ResetBufferProcessor( &stream->bufferProcessor );
+
+ if( stream->callbackMode )
+ {
+ /* Create and start callback engine thread */
+ /* Also waits 1 second for stream to be started by engine thread (otherwise aborts) */
+ PA_ENSURE_( PaUnixThread_New( &stream->thread, &CallbackThreadFunc, stream, 1., 0 /*rtSched*/ ) );
+ }
+ else
+ {
+ PA_ENSURE_( PaAsiHpi_StartStream( stream, 0 ) );
+ }
+
+error:
+ return result;
+}
+
+
+/** Stop HPI streams (input + output), either softly or abruptly.
+ If abort is false, the function blocks until the output stream is drained, otherwise it
+ stops immediately and discards data in the stream hardware buffers.
+
+ This function is safe to call from the callback engine thread as well as the main thread.
+
+ @param stream Pointer to stream struct
+
+ @param abort True if samples in output buffer should be discarded (otherwise blocks until stream is done)
+
+ @return PortAudio error code
+
+ */
+static PaError PaAsiHpi_StopStream( PaAsiHpiStream *stream, int abort )
+{
+ PaError result = paNoError;
+
+ assert( stream );
+
+ /* Input channels */
+ if( stream->input )
+ {
+ PA_ASIHPI_UNLESS_( HPI_InStreamReset( NULL,
+ stream->input->hpiStream ), paUnanticipatedHostError );
+ }
+ /* Output channels */
+ if( stream->output )
+ {
+ if( !abort )
+ {
+ /* Wait until HPI output stream is drained */
+ while( 1 )
+ {
+ PaAsiHpiStreamInfo streamInfo;
+ PaTime timeLeft;
+
+ /* Obtain number of samples waiting to be played */
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &streamInfo ) );
+ /* Check if stream is drained */
+ if( (streamInfo.state != HPI_STATE_PLAYING) &&
+ (streamInfo.dataSize < stream->output->bytesPerFrame * PA_ASIHPI_MIN_FRAMES_) )
+ break;
+ /* Sleep amount of time represented by remaining samples */
+ timeLeft = 1000.0 * streamInfo.dataSize / stream->output->bytesPerFrame
+ / stream->baseStreamRep.streamInfo.sampleRate;
+ Pa_Sleep( (long)ceil( timeLeft ) );
+ }
+ }
+ PA_ASIHPI_UNLESS_( HPI_OutStreamReset( NULL,
+ stream->output->hpiStream ), paUnanticipatedHostError );
+ }
+
+ /* Report stream info for debugging purposes */
+ /* PaAsiHpi_StreamDump( stream ); */
+
+error:
+ return result;
+}
+
+
+/** Stop or abort PortAudio stream.
+
+ This function is used to explicitly stop the PortAudio stream (via StopStream/AbortStream),
+ as opposed to the situation when the callback finishes with a result other than paContinue.
+ If a stream is in callback mode we will have to inspect whether the background thread has
+ finished, or we will have to take it out. In either case we join the thread before returning.
+ In blocking mode, we simply tell HPI to stop abruptly (abort) or finish buffers (drain).
+ The PortAudio stream will be in the Stopped state after a call to this function.
+
+ Don't call this from the callback engine thread!
+
+ @param stream Pointer to stream struct
+
+ @param abort True if samples in output buffer should be discarded (otherwise blocks until stream is done)
+
+ @return PortAudio error code
+*/
+static PaError PaAsiHpi_ExplicitStop( PaAsiHpiStream *stream, int abort )
+{
+ PaError result = paNoError;
+
+ /* First deal with the callback thread, cancelling and/or joining it if necessary */
+ if( stream->callbackMode )
+ {
+ PaError threadRes;
+ stream->callbackAbort = abort;
+ if( abort )
+ {
+ PA_DEBUG(( "Aborting callback\n" ));
+ }
+ else
+ {
+ PA_DEBUG(( "Stopping callback\n" ));
+ }
+ PA_ENSURE_( PaUnixThread_Terminate( &stream->thread, !abort, &threadRes ) );
+ if( threadRes != paNoError )
+ {
+ PA_DEBUG(( "Callback thread returned: %d\n", threadRes ));
+ }
+ }
+ else
+ {
+ PA_ENSURE_( PaAsiHpi_StopStream( stream, abort ) );
+ }
+
+ stream->state = paAsiHpiStoppedState;
+
+error:
+ return result;
+}
+
+
+/** Stop PortAudio stream.
+ This blocks until the output buffers are drained.
+
+ @param s Pointer to PortAudio stream
+
+ @return PortAudio error code
+*/
+static PaError StopStream( PaStream *s )
+{
+ return PaAsiHpi_ExplicitStop( (PaAsiHpiStream *) s, 0 );
+}
+
+
+/** Abort PortAudio stream.
+ This discards any existing data in output buffers and stops the stream immediately.
+
+ @param s Pointer to PortAudio stream
+
+ @return PortAudio error code
+*/
+static PaError AbortStream( PaStream *s )
+{
+ return PaAsiHpi_ExplicitStop( (PaAsiHpiStream * ) s, 1 );
+}
+
+
+/** Determine whether the stream is stopped.
+ A stream is considered to be stopped prior to a successful call to StartStream and after
+ a successful call to StopStream or AbortStream. If a stream callback returns a value other
+ than paContinue the stream is NOT considered to be stopped (it is in CallbackFinished state).
+
+ @param s Pointer to PortAudio stream
+
+ @return Returns one (1) when the stream is stopped, zero (0) when the stream is running, or
+ a PaErrorCode (which are always negative) if PortAudio is not initialized or an
+ error is encountered.
+*/
+static PaError IsStreamStopped( PaStream *s )
+{
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+ assert( stream );
+ return stream->state == paAsiHpiStoppedState ? 1 : 0;
+}
+
+
+/** Determine whether the stream is active.
+ A stream is active after a successful call to StartStream(), until it becomes inactive either
+ as a result of a call to StopStream() or AbortStream(), or as a result of a return value
+ other than paContinue from the stream callback. In the latter case, the stream is considered
+ inactive after the last buffer has finished playing.
+
+ @param s Pointer to PortAudio stream
+
+ @return Returns one (1) when the stream is active (i.e. playing or recording audio),
+ zero (0) when not playing, or a PaErrorCode (which are always negative)
+ if PortAudio is not initialized or an error is encountered.
+*/
+static PaError IsStreamActive( PaStream *s )
+{
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+ assert( stream );
+ return stream->state == paAsiHpiActiveState ? 1 : 0;
+}
+
+
+/** Returns current stream time.
+ This corresponds to the system clock. The clock should run continuously while the stream
+ is open, i.e. between calls to OpenStream() and CloseStream(), therefore a frame counter
+ is not good enough.
+
+ @param s Pointer to PortAudio stream
+
+ @return Stream time, in seconds
+ */
+static PaTime GetStreamTime( PaStream *s )
+{
+ return PaUtil_GetTime();
+}
+
+
+/** Returns CPU load.
+
+ @param s Pointer to PortAudio stream
+
+ @return CPU load (0.0 if blocking interface is used)
+ */
+static double GetStreamCpuLoad( PaStream *s )
+{
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+
+ return stream->callbackMode ? PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ) : 0.0;
+}
+
+/* --------------------------- Callback Interface --------------------------- */
+
+/** Exit routine which is called when callback thread quits.
+ This takes care of stopping the HPI streams (either waiting for output to finish, or
+ abruptly). It also calls the user-supplied StreamFinished callback, and sets the
+ stream state to CallbackFinished if it was reached via a non-paContinue return from
+ the user callback function.
+
+ @param userData A pointer to an open stream previously created with Pa_OpenStream
+ */
+static void PaAsiHpi_OnThreadExit( void *userData )
+{
+ PaAsiHpiStream *stream = (PaAsiHpiStream *) userData;
+
+ assert( stream );
+
+ PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer );
+
+ PA_DEBUG(( "%s: Stopping HPI streams\n", __FUNCTION__ ));
+ PaAsiHpi_StopStream( stream, stream->callbackAbort );
+ PA_DEBUG(( "%s: Stoppage\n", __FUNCTION__ ));
+
+ /* Eventually notify user all buffers have played */
+ if( stream->baseStreamRep.streamFinishedCallback )
+ {
+ stream->baseStreamRep.streamFinishedCallback( stream->baseStreamRep.userData );
+ }
+
+ /* Unfortunately both explicit calls to Stop/AbortStream (leading to Stopped state)
+ and implicit stops via paComplete/paAbort (leading to CallbackFinished state)
+ end up here - need another flag to remind us which is the case */
+ if( stream->callbackFinished )
+ stream->state = paAsiHpiCallbackFinishedState;
+}
+
+
+/** Wait until there is enough frames to fill a host buffer.
+ The routine attempts to sleep until at least a full host buffer can be retrieved from the
+ input HPI stream and passed to the output HPI stream. It will first sleep until enough
+ output space is available, as this is usually easily achievable. If it is an output-only
+ stream, it will also sleep if the hardware buffer is too full, thereby throttling the
+ filling of the output buffer and reducing output latency. The routine then blocks until
+ enough input samples are available, unless this will cause an output underflow. In the
+ process, input overflows and output underflows are indicated.
+
+ @param stream Pointer to stream struct
+
+ @param framesAvail Returns the number of available frames
+
+ @param cbFlags Overflows and underflows indicated in here
+
+ @return PortAudio error code (only paUnanticipatedHostError expected)
+ */
+static PaError PaAsiHpi_WaitForFrames( PaAsiHpiStream *stream, unsigned long *framesAvail,
+ PaStreamCallbackFlags *cbFlags )
+{
+ PaError result = paNoError;
+ double sampleRate;
+ unsigned long framesTarget;
+ uint32_t outputData = 0, outputSpace = 0, inputData = 0, framesLeft = 0;
+
+ assert( stream );
+ assert( stream->input || stream->output );
+
+ sampleRate = stream->baseStreamRep.streamInfo.sampleRate;
+ /* We have to come up with this much frames on both input and output */
+ framesTarget = stream->bufferProcessor.framesPerHostBuffer;
+ assert( framesTarget > 0 );
+
+ while( 1 )
+ {
+ PaAsiHpiStreamInfo info;
+ /* Check output first, as this takes priority in the default full-duplex mode */
+ if( stream->output )
+ {
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &info ) );
+ /* Wait until enough space is available in output buffer to receive a full block */
+ if( info.availableFrames < framesTarget )
+ {
+ framesLeft = framesTarget - info.availableFrames;
+ Pa_Sleep( (long)ceil( 1000 * framesLeft / sampleRate ) );
+ continue;
+ }
+ /* Wait until the data in hardware buffer has dropped to a sensible level.
+ Without this, the hardware buffer quickly fills up in the absence of an input
+ stream to regulate its data rate (if data generation is fast). This leads to
+ large latencies, as the AudioScience hardware buffers are humongous.
+ This is similar to the default "Hardware Buffering=off" option in the
+ AudioScience WAV driver. */
+ if( !stream->input && (stream->output->outputBufferCap > 0) &&
+ ( info.totalBufferedData > stream->output->outputBufferCap / stream->output->bytesPerFrame ) )
+ {
+ framesLeft = info.totalBufferedData - stream->output->outputBufferCap / stream->output->bytesPerFrame;
+ Pa_Sleep( (long)ceil( 1000 * framesLeft / sampleRate ) );
+ continue;
+ }
+ outputData = info.totalBufferedData;
+ outputSpace = info.availableFrames;
+ /* Report output underflow to callback */
+ if( info.underflow )
+ {
+ *cbFlags |= paOutputUnderflow;
+ }
+ }
+
+ /* Now check input side */
+ if( stream->input )
+ {
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->input, &info ) );
+ /* If a full block of samples hasn't been recorded yet, wait for it if possible */
+ if( info.availableFrames < framesTarget )
+ {
+ framesLeft = framesTarget - info.availableFrames;
+ /* As long as output is not disrupted in the process, wait for a full
+ block of input samples */
+ if( !stream->output || (outputData > framesLeft) )
+ {
+ Pa_Sleep( (long)ceil( 1000 * framesLeft / sampleRate ) );
+ continue;
+ }
+ }
+ inputData = info.availableFrames;
+ /** @todo The paInputOverflow flag should be set in the callback containing the
+ first input sample following the overflow. That means the block currently sitting
+ at the fore-front of recording, i.e. typically the one containing the newest (last)
+ sample in the HPI buffer system. This is most likely not the same as the current
+ block of data being passed to the callback. The current overflow should ideally
+ be noted in an overflow list of sorts, with an indication of when it should be
+ reported. The trouble starts if there are several separate overflow incidents,
+ given a big input buffer. Oh well, something to try out later... */
+ if( info.overflow )
+ {
+ *cbFlags |= paInputOverflow;
+ }
+ }
+ break;
+ }
+ /* Full-duplex stream */
+ if( stream->input && stream->output )
+ {
+ if( outputSpace >= framesTarget )
+ *framesAvail = outputSpace;
+ /* If input didn't make the target, keep the output count instead (input underflow) */
+ if( (inputData >= framesTarget) && (inputData < outputSpace) )
+ *framesAvail = inputData;
+ }
+ else
+ {
+ *framesAvail = stream->input ? inputData : outputSpace;
+ }
+
+error:
+ return result;
+}
+
+
+/** Obtain recording, current and playback timestamps of stream.
+ The current time is determined by the system clock. This "now" timestamp occurs at the
+ forefront of recording (and playback in the full-duplex case), which happens later than the
+ input timestamp by an amount equal to the total number of recorded frames in the input buffer.
+ The output timestamp indicates when the next generated sample will actually be played. This
+ happens after all the samples currently in the output buffer are played. The output timestamp
+ therefore follows the current timestamp by an amount equal to the number of frames yet to be
+ played back in the output buffer.
+
+ If the current timestamp is the present, the input timestamp is in the past and the output
+ timestamp is in the future.
+
+ @param stream Pointer to stream struct
+
+ @param timeInfo Pointer to timeInfo struct that will contain timestamps
+ */
+static void PaAsiHpi_CalculateTimeInfo( PaAsiHpiStream *stream, PaStreamCallbackTimeInfo *timeInfo )
+{
+ PaAsiHpiStreamInfo streamInfo;
+ double sampleRate;
+
+ assert( stream );
+ assert( timeInfo );
+ sampleRate = stream->baseStreamRep.streamInfo.sampleRate;
+
+ /* The current time ("now") is at the forefront of both recording and playback */
+ timeInfo->currentTime = GetStreamTime( (PaStream *)stream );
+ /* The last sample in the input buffer was recorded just now, so the first sample
+ happened (number of recorded samples)/sampleRate ago */
+ timeInfo->inputBufferAdcTime = timeInfo->currentTime;
+ if( stream->input )
+ {
+ PaAsiHpi_GetStreamInfo( stream->input, &streamInfo );
+ timeInfo->inputBufferAdcTime -= streamInfo.totalBufferedData / sampleRate;
+ }
+ /* The first of the outgoing samples will be played after all the samples in the output
+ buffer is done */
+ timeInfo->outputBufferDacTime = timeInfo->currentTime;
+ if( stream->output )
+ {
+ PaAsiHpi_GetStreamInfo( stream->output, &streamInfo );
+ timeInfo->outputBufferDacTime += streamInfo.totalBufferedData / sampleRate;
+ }
+}
+
+
+/** Read from HPI input stream and register buffers.
+ This reads data from the HPI input stream (if it exists) and registers the temp stream
+ buffers of both input and output streams with the buffer processor. In the process it also
+ handles input underflows in the full-duplex case.
+
+ @param stream Pointer to stream struct
+
+ @param numFrames On entrance the number of available frames, on exit the number of
+ received frames
+
+ @param cbFlags Indicates overflows and underflows
+
+ @return PortAudio error code
+ */
+static PaError PaAsiHpi_BeginProcessing( PaAsiHpiStream *stream, unsigned long *numFrames,
+ PaStreamCallbackFlags *cbFlags )
+{
+ PaError result = paNoError;
+
+ assert( stream );
+ if( *numFrames > stream->maxFramesPerHostBuffer )
+ *numFrames = stream->maxFramesPerHostBuffer;
+
+ if( stream->input )
+ {
+ PaAsiHpiStreamInfo info;
+
+ uint32_t framesToGet = *numFrames;
+
+ /* Check for overflows and underflows yet again */
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->input, &info ) );
+ if( info.overflow )
+ {
+ *cbFlags |= paInputOverflow;
+ }
+ /* Input underflow if less than expected number of samples pitch up */
+ if( framesToGet > info.availableFrames )
+ {
+ PaUtilZeroer *zeroer;
+ PaSampleFormat inputFormat;
+
+ /* Never call an input-only stream with InputUnderflow set */
+ if( stream->output )
+ *cbFlags |= paInputUnderflow;
+ framesToGet = info.availableFrames;
+ /* Fill temp buffer with silence (to make up for missing input samples) */
+ inputFormat = PaAsiHpi_HpiToPaFormat( stream->input->hpiFormat.wFormat );
+ zeroer = PaUtil_SelectZeroer( inputFormat );
+ zeroer(stream->input->tempBuffer, 1,
+ stream->input->tempBufferSize / Pa_GetSampleSize(inputFormat) );
+ }
+
+ /* Read block of data into temp buffer */
+ PA_ASIHPI_UNLESS_( HPI_InStreamReadBuf( NULL,
+ stream->input->hpiStream,
+ stream->input->tempBuffer,
+ framesToGet * stream->input->bytesPerFrame),
+ paUnanticipatedHostError );
+ /* Register temp buffer with buffer processor (always FULL buffer) */
+ PaUtil_SetInputFrameCount( &stream->bufferProcessor, *numFrames );
+ /* HPI interface only allows interleaved channels */
+ PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor,
+ 0, stream->input->tempBuffer,
+ stream->input->hpiFormat.wChannels );
+ }
+ if( stream->output )
+ {
+ /* Register temp buffer with buffer processor */
+ PaUtil_SetOutputFrameCount( &stream->bufferProcessor, *numFrames );
+ /* HPI interface only allows interleaved channels */
+ PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor,
+ 0, stream->output->tempBuffer,
+ stream->output->hpiFormat.wChannels );
+ }
+
+error:
+ return result;
+}
+
+
+/** Flush output buffers to HPI output stream.
+ This completes the processing cycle by writing the temp buffer to the HPI interface.
+ Additional output underflows are caught before data is written to the stream, as this
+ action typically remedies the underflow and hides it in the process.
+
+ @param stream Pointer to stream struct
+
+ @param numFrames The number of frames to write to the output stream
+
+ @param cbFlags Indicates overflows and underflows
+ */
+static PaError PaAsiHpi_EndProcessing( PaAsiHpiStream *stream, unsigned long numFrames,
+ PaStreamCallbackFlags *cbFlags )
+{
+ PaError result = paNoError;
+
+ assert( stream );
+
+ if( stream->output )
+ {
+ PaAsiHpiStreamInfo info;
+ /* Check for underflows after the (potentially time-consuming) callback */
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &info ) );
+ if( info.underflow )
+ {
+ *cbFlags |= paOutputUnderflow;
+ }
+
+ /* Write temp buffer to HPI stream */
+ PA_ASIHPI_UNLESS_( HPI_OutStreamWriteBuf( NULL,
+ stream->output->hpiStream,
+ stream->output->tempBuffer,
+ numFrames * stream->output->bytesPerFrame,
+ &stream->output->hpiFormat),
+ paUnanticipatedHostError );
+ }
+
+error:
+ return result;
+}
+
+
+/** Main callback engine.
+ This function runs in a separate thread and does all the work of fetching audio data from
+ the AudioScience card via the HPI interface, feeding it to the user callback via the buffer
+ processor, and delivering the resulting output data back to the card via HPI calls.
+ It is started and terminated when the PortAudio stream is started and stopped, and starts
+ the HPI streams on startup.
+
+ @param userData A pointer to an open stream previously created with Pa_OpenStream.
+*/
+static void *CallbackThreadFunc( void *userData )
+{
+ PaError result = paNoError;
+ PaAsiHpiStream *stream = (PaAsiHpiStream *) userData;
+ int callbackResult = paContinue;
+
+ assert( stream );
+
+ /* Cleanup routine stops streams on thread exit */
+ pthread_cleanup_push( &PaAsiHpi_OnThreadExit, stream );
+
+ /* Start HPI streams and notify parent when we're done */
+ PA_ENSURE_( PaUnixThread_PrepareNotify( &stream->thread ) );
+ /* Buffer will be primed with silence */
+ PA_ENSURE_( PaAsiHpi_StartStream( stream, 0 ) );
+ PA_ENSURE_( PaUnixThread_NotifyParent( &stream->thread ) );
+
+ /* MAIN LOOP */
+ while( 1 )
+ {
+ PaStreamCallbackFlags cbFlags = 0;
+ unsigned long framesAvail, framesGot;
+
+ pthread_testcancel();
+
+ /** @concern StreamStop if the main thread has requested a stop and the stream has not
+ * been effectively stopped we signal this condition by modifying callbackResult
+ * (we'll want to flush buffered output). */
+ if( PaUnixThread_StopRequested( &stream->thread ) && (callbackResult == paContinue) )
+ {
+ PA_DEBUG(( "Setting callbackResult to paComplete\n" ));
+ callbackResult = paComplete;
+ }
+
+ /* Start winding down thread if requested */
+ if( callbackResult != paContinue )
+ {
+ stream->callbackAbort = (callbackResult == paAbort);
+ if( stream->callbackAbort ||
+ /** @concern BlockAdaption: Go on if adaption buffers are empty */
+ PaUtil_IsBufferProcessorOutputEmpty( &stream->bufferProcessor ) )
+ {
+ goto end;
+ }
+ PA_DEBUG(( "%s: Flushing buffer processor\n", __FUNCTION__ ));
+ /* There is still buffered output that needs to be processed */
+ }
+
+ /* SLEEP */
+ /* Wait for data (or buffer space) to become available. This basically sleeps and
+ polls the HPI interface until a full block of frames can be moved. */
+ PA_ENSURE_( PaAsiHpi_WaitForFrames( stream, &framesAvail, &cbFlags ) );
+
+ /* Consume buffer space. Once we have a number of frames available for consumption we
+ must retrieve the data from the HPI interface and pass it to the PA buffer processor.
+ We should be prepared to process several chunks successively. */
+ while( framesAvail > 0 )
+ {
+ PaStreamCallbackTimeInfo timeInfo = {0, 0, 0};
+
+ pthread_testcancel();
+
+ framesGot = framesAvail;
+ if( stream->bufferProcessor.hostBufferSizeMode == paUtilFixedHostBufferSize )
+ {
+ /* We've committed to a fixed host buffer size, stick to that */
+ framesGot = framesGot >= stream->maxFramesPerHostBuffer ? stream->maxFramesPerHostBuffer : 0;
+ }
+ else
+ {
+ /* We've committed to an upper bound on the size of host buffers */
+ assert( stream->bufferProcessor.hostBufferSizeMode == paUtilBoundedHostBufferSize );
+ framesGot = PA_MIN( framesGot, stream->maxFramesPerHostBuffer );
+ }
+
+ /* Obtain buffer timestamps */
+ PaAsiHpi_CalculateTimeInfo( stream, &timeInfo );
+ PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, cbFlags );
+ /* CPU load measurement should include processing activivity external to the stream callback */
+ PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
+ if( framesGot > 0 )
+ {
+ /* READ FROM HPI INPUT STREAM */
+ PA_ENSURE_( PaAsiHpi_BeginProcessing( stream, &framesGot, &cbFlags ) );
+ /* Input overflow in a full-duplex stream makes for interesting times */
+ if( stream->input && stream->output && (cbFlags & paInputOverflow) )
+ {
+ /* Special full-duplex paNeverDropInput mode */
+ if( stream->neverDropInput )
+ {
+ PaUtil_SetNoOutput( &stream->bufferProcessor );
+ cbFlags |= paOutputOverflow;
+ }
+ }
+ /* CALL USER CALLBACK WITH INPUT DATA, AND OBTAIN OUTPUT DATA */
+ PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult );
+ /* Clear overflow and underflow information (but PaAsiHpi_EndProcessing might
+ still show up output underflow that will carry over to next round) */
+ cbFlags = 0;
+ /* WRITE TO HPI OUTPUT STREAM */
+ PA_ENSURE_( PaAsiHpi_EndProcessing( stream, framesGot, &cbFlags ) );
+ /* Advance frame counter */
+ framesAvail -= framesGot;
+ }
+ PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesGot );
+
+ if( framesGot == 0 )
+ {
+ /* Go back to polling for more frames */
+ break;
+
+ }
+ if( callbackResult != paContinue )
+ break;
+ }
+ }
+
+ /* This code is unreachable, but important to include regardless because it
+ * is possibly a macro with a closing brace to match the opening brace in
+ * pthread_cleanup_push() above. The documentation states that they must
+ * always occur in pairs. */
+ pthread_cleanup_pop( 1 );
+
+end:
+ /* Indicates normal exit of callback, as opposed to the thread getting killed explicitly */
+ stream->callbackFinished = 1;
+ PA_DEBUG(( "%s: Thread %d exiting (callbackResult = %d)\n ",
+ __FUNCTION__, pthread_self(), callbackResult ));
+ /* Exit from thread and report any PortAudio error in the process */
+ PaUnixThreading_EXIT( result );
+error:
+ goto end;
+}
+
+/* --------------------------- Blocking Interface --------------------------- */
+
+/* As separate stream interfaces are used for blocking and callback streams, the following
+ functions can be guaranteed to only be called for blocking streams. */
+
+/** Read data from input stream.
+ This reads the indicated number of frames into the supplied buffer from an input stream,
+ and blocks until this is done.
+
+ @param s Pointer to PortAudio stream
+
+ @param buffer Pointer to buffer that will receive interleaved data (or an array of pointers
+ to a buffer for each non-interleaved channel)
+
+ @param frames Number of frames to read from stream
+
+ @return PortAudio error code (also indicates overflow via paInputOverflowed)
+ */
+static PaError ReadStream( PaStream *s,
+ void *buffer,
+ unsigned long frames )
+{
+ PaError result = paNoError;
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+ PaAsiHpiStreamInfo info;
+ void *userBuffer;
+
+ assert( stream );
+ PA_UNLESS_( stream->input, paCanNotReadFromAnOutputOnlyStream );
+
+ /* Check for input overflow since previous call to ReadStream */
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->input, &info ) );
+ if( info.overflow )
+ {
+ result = paInputOverflowed;
+ }
+
+ /* NB Make copy of user buffer pointers, since they are advanced by buffer processor */
+ if( stream->bufferProcessor.userInputIsInterleaved )
+ {
+ userBuffer = buffer;
+ }
+ else
+ {
+ /* Copy channels into local array */
+ userBuffer = stream->blockingUserBufferCopy;
+ memcpy( userBuffer, buffer, sizeof (void *) * stream->input->hpiFormat.wChannels );
+ }
+
+ while( frames > 0 )
+ {
+ unsigned long framesGot, framesAvail;
+ PaStreamCallbackFlags cbFlags = 0;
+
+ PA_ENSURE_( PaAsiHpi_WaitForFrames( stream, &framesAvail, &cbFlags ) );
+ framesGot = PA_MIN( framesAvail, frames );
+ PA_ENSURE_( PaAsiHpi_BeginProcessing( stream, &framesGot, &cbFlags ) );
+
+ if( framesGot > 0 )
+ {
+ framesGot = PaUtil_CopyInput( &stream->bufferProcessor, &userBuffer, framesGot );
+ PA_ENSURE_( PaAsiHpi_EndProcessing( stream, framesGot, &cbFlags ) );
+ /* Advance frame counter */
+ frames -= framesGot;
+ }
+ }
+
+error:
+ return result;
+}
+
+
+/** Write data to output stream.
+ This writes the indicated number of frames from the supplied buffer to an output stream,
+ and blocks until this is done.
+
+ @param s Pointer to PortAudio stream
+
+ @param buffer Pointer to buffer that provides interleaved data (or an array of pointers
+ to a buffer for each non-interleaved channel)
+
+ @param frames Number of frames to write to stream
+
+ @return PortAudio error code (also indicates underflow via paOutputUnderflowed)
+ */
+static PaError WriteStream( PaStream *s,
+ const void *buffer,
+ unsigned long frames )
+{
+ PaError result = paNoError;
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+ PaAsiHpiStreamInfo info;
+ const void *userBuffer;
+
+ assert( stream );
+ PA_UNLESS_( stream->output, paCanNotWriteToAnInputOnlyStream );
+
+ /* Check for output underflow since previous call to WriteStream */
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &info ) );
+ if( info.underflow )
+ {
+ result = paOutputUnderflowed;
+ }
+
+ /* NB Make copy of user buffer pointers, since they are advanced by buffer processor */
+ if( stream->bufferProcessor.userOutputIsInterleaved )
+ {
+ userBuffer = buffer;
+ }
+ else
+ {
+ /* Copy channels into local array */
+ userBuffer = stream->blockingUserBufferCopy;
+ memcpy( (void *)userBuffer, buffer, sizeof (void *) * stream->output->hpiFormat.wChannels );
+ }
+
+ while( frames > 0 )
+ {
+ unsigned long framesGot, framesAvail;
+ PaStreamCallbackFlags cbFlags = 0;
+
+ PA_ENSURE_( PaAsiHpi_WaitForFrames( stream, &framesAvail, &cbFlags ) );
+ framesGot = PA_MIN( framesAvail, frames );
+ PA_ENSURE_( PaAsiHpi_BeginProcessing( stream, &framesGot, &cbFlags ) );
+
+ if( framesGot > 0 )
+ {
+ framesGot = PaUtil_CopyOutput( &stream->bufferProcessor, &userBuffer, framesGot );
+ PA_ENSURE_( PaAsiHpi_EndProcessing( stream, framesGot, &cbFlags ) );
+ /* Advance frame counter */
+ frames -= framesGot;
+ }
+ }
+
+error:
+ return result;
+}
+
+
+/** Number of frames that can be read from input stream without blocking.
+
+ @param s Pointer to PortAudio stream
+
+ @return Number of frames, or PortAudio error code
+ */
+static signed long GetStreamReadAvailable( PaStream *s )
+{
+ PaError result = paNoError;
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+ PaAsiHpiStreamInfo info;
+
+ assert( stream );
+ PA_UNLESS_( stream->input, paCanNotReadFromAnOutputOnlyStream );
+
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->input, &info ) );
+ /* Round down to the nearest host buffer multiple */
+ result = (info.availableFrames / stream->maxFramesPerHostBuffer) * stream->maxFramesPerHostBuffer;
+ if( info.overflow )
+ {
+ result = paInputOverflowed;
+ }
+
+error:
+ return result;
+}
+
+
+/** Number of frames that can be written to output stream without blocking.
+
+ @param s Pointer to PortAudio stream
+
+ @return Number of frames, or PortAudio error code
+ */
+static signed long GetStreamWriteAvailable( PaStream *s )
+{
+ PaError result = paNoError;
+ PaAsiHpiStream *stream = (PaAsiHpiStream*)s;
+ PaAsiHpiStreamInfo info;
+
+ assert( stream );
+ PA_UNLESS_( stream->output, paCanNotWriteToAnInputOnlyStream );
+
+ PA_ENSURE_( PaAsiHpi_GetStreamInfo( stream->output, &info ) );
+ /* Round down to the nearest host buffer multiple */
+ result = (info.availableFrames / stream->maxFramesPerHostBuffer) * stream->maxFramesPerHostBuffer;
+ if( info.underflow )
+ {
+ result = paOutputUnderflowed;
+ }
+
+error:
+ return result;
+}