Spaces:
Runtime error
Runtime error
/* | |
* $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 <[email protected]> | |
* Copyright (c) 2005,2006 Ludwig Schwardt <[email protected]> | |
* Copyright (c) 2011 Eliot Blennerhassett <[email protected]> | |
* | |
* 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. | |
*/ | |
/* -------------------------------------------------------------------------- */ | |
/* | |
* Defines | |
*/ | |
/* Error reporting and assertions */ | |
/** Evaluate expression, and return on any PortAudio errors */ | |
/** Assert expression, else return the provided PaError */ | |
/** Check return value of HPI function, and map it to PaError */ | |
/** Report HPI error code and text */ | |
/* Defaults */ | |
/** Sample formats available natively on AudioScience hardware */ | |
/** Enable background bus mastering (BBM) for buffer transfers, if available (see HPI docs) */ | |
/** Minimum number of frames in HPI buffer (for either data or available space). | |
If buffer contains less data/space, it indicates xrun or completion. */ | |
/** Minimum polling interval in milliseconds, which determines minimum host buffer size */ | |
/* -------------------------------------------------------------------------- */ | |
/* | |
* Structures | |
*/ | |
/** Host API global data */ | |
typedef struct PaAsiHpiHostApiRepresentation | |
{ | |
/* PortAudio "base class" - keep the baseRep first! (C-style inheritance) */ | |
PaUtilHostApiRepresentation baseHostApiRep; | |
PaUtilStreamInterface callbackStreamInterface; | |
PaUtilStreamInterface blockingStreamInterface; | |
PaUtilAllocationGroup *allocations; | |
/* implementation specific data goes here */ | |
PaHostApiIndex hostApiIndex; | |
} | |
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 | |
*/ | |
extern "C" | |
{ | |
/* The only exposed function in the entire host API implementation */ | |
PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); | |
} | |
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; | |
} | |