diff options
Diffstat (limited to 'portaudio/src/hostapi/asihpi/pa_linux_asihpi.c')
-rw-r--r-- | portaudio/src/hostapi/asihpi/pa_linux_asihpi.c | 2893 |
1 files changed, 0 insertions, 2893 deletions
diff --git a/portaudio/src/hostapi/asihpi/pa_linux_asihpi.c b/portaudio/src/hostapi/asihpi/pa_linux_asihpi.c deleted file mode 100644 index 79eb1d7..0000000 --- a/portaudio/src/hostapi/asihpi/pa_linux_asihpi.c +++ /dev/null @@ -1,2893 +0,0 @@ -/* - * $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; -} |