diff options
Diffstat (limited to 'pd/portaudio/pa_linux_alsa/pa_linux_alsa.c')
-rw-r--r-- | pd/portaudio/pa_linux_alsa/pa_linux_alsa.c | 989 |
1 files changed, 989 insertions, 0 deletions
diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c new file mode 100644 index 00000000..9582b5b8 --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.c @@ -0,0 +1,989 @@ +/* + * $Id: pa_linux_alsa.c,v 1.1.2.3 2003/02/01 21:55:03 joshua Exp $ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * ALSA implementation by Joshua Haberman + * + * Copyright (c) 2002 Joshua Haberman <joshua@haberman.com> + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <sys/poll.h> +#include <pthread.h> + +#include <string.h> /* strlen() */ +#include <limits.h> + +#include <alsa/asoundlib.h> + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_linux_alsa.h" + +/* PaAlsaHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation commonHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + PaHostApiIndex hostApiIndex; +} +PaAlsaHostApiRepresentation; + + +/* prototypes for functions declared in this file */ + +PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ); +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *callback, + void *userData ); +static PaError CloseStream( PaStream* stream ); +static PaError StartStream( PaStream *stream ); +static PaError StopStream( PaStream *stream ); +static PaError AbortStream( PaStream *stream ); +static PaError IsStreamStopped( PaStream *s ); +static PaError IsStreamActive( PaStream *stream ); +static PaTime GetStreamTime( PaStream *stream ); +static double GetStreamCpuLoad( PaStream* stream ); +static PaError BuildDeviceList( PaAlsaHostApiRepresentation *hostApi ); + +/* blocking calls are in blocking_calls.c */ +extern PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +extern PaError WriteStream( PaStream* stream, void *buffer, unsigned long frames ); +extern signed long GetStreamReadAvailable( PaStream* stream ); +extern signed long GetStreamWriteAvailable( PaStream* stream ); + +/* all callback-related functions are in callback_thread.c */ +extern void *CallbackThread( void *userData ); + + +PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaAlsaHostApiRepresentation *skeletonHostApi; + + skeletonHostApi = (PaAlsaHostApiRepresentation*) + PaUtil_AllocateMemory( sizeof(PaAlsaHostApiRepresentation) ); + if( !skeletonHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + skeletonHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !skeletonHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + skeletonHostApi->hostApiIndex = hostApiIndex; + *hostApi = (PaUtilHostApiRepresentation*)skeletonHostApi; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paALSA; + (*hostApi)->info.name = "ALSA implementation"; + + BuildDeviceList( skeletonHostApi ); + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + + PaUtil_InitializeStreamInterface( &skeletonHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyReadWrite, PaUtil_DummyReadWrite, + PaUtil_DummyGetAvailable, + PaUtil_DummyGetAvailable ); + + PaUtil_InitializeStreamInterface( &skeletonHostApi->blockingStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, + IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, + GetStreamReadAvailable, + GetStreamWriteAvailable ); + + return result; + +error: + if( skeletonHostApi ) + { + if( skeletonHostApi->allocations ) + { + PaUtil_FreeAllAllocations( skeletonHostApi->allocations ); + PaUtil_DestroyAllocationGroup( skeletonHostApi->allocations ); + } + + PaUtil_FreeMemory( skeletonHostApi ); + } + return result; +} + +static PaError BuildDeviceList( PaAlsaHostApiRepresentation *alsaApi ) +{ + PaUtilHostApiRepresentation *commonApi = &alsaApi->commonHostApiRep; + PaDeviceInfo *deviceInfoArray; + int deviceCount = 0; + int card_idx; + int device_idx; + snd_ctl_t *ctl; + snd_ctl_card_info_t *card_info; + + /* count the devices by enumerating all the card numbers */ + + /* snd_card_next() modifies the integer passed to it to be: + * the index of the first card if the parameter is -1 + * the index of the next card if the parameter is the index of a card + * -1 if there are no more cards + * + * The function itself returns 0 if it succeeded. */ + card_idx = -1; + while( snd_card_next( &card_idx ) == 0 && card_idx >= 0 ) + { + deviceCount++; + } + + /* allocate deviceInfo memory based on the number of devices */ + + commonApi->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ); + if( !commonApi->deviceInfos ) + { + return paInsufficientMemory; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + alsaApi->allocations, sizeof(PaDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + return paInsufficientMemory; + } + + /* now loop over the list of devices again, filling in the deviceInfo for each */ + card_idx = -1; + device_idx = 0; + while( snd_card_next( &card_idx ) == 0 && card_idx >= 0 ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[device_idx]; + char *deviceName; + char alsaDeviceName[50]; + const char *cardName; + + commonApi->deviceInfos[device_idx++] = deviceInfo; + + deviceInfo->structVersion = 2; + deviceInfo->hostApi = alsaApi->hostApiIndex; + + sprintf( alsaDeviceName, "hw:%d", card_idx ); + snd_ctl_open( &ctl, alsaDeviceName, 0 ); + snd_ctl_card_info_malloc( &card_info ); + snd_ctl_card_info( ctl, card_info ); + cardName = snd_ctl_card_info_get_id( card_info ); + + deviceName = (char*)PaUtil_GroupAllocateMemory( alsaApi->allocations, + strlen(cardName) + 1 ); + if( !deviceName ) + { + return paInsufficientMemory; + } + strcpy( deviceName, cardName ); + deviceInfo->name = deviceName; + + snd_ctl_card_info_free( card_info ); + + /* to determine max. channels, we must open the device and query the + * hardware parameter configuration space */ + { + snd_pcm_t *pcm_handle; + snd_pcm_hw_params_t *hw_params; + int dir; + + snd_pcm_hw_params_malloc( &hw_params ); + + /* get max channels for capture */ + + if( snd_pcm_open( &pcm_handle, alsaDeviceName, SND_PCM_STREAM_CAPTURE, 0 ) < 0 ) + { + deviceInfo->maxInputChannels = 0; + } + else + { + snd_pcm_hw_params_any( pcm_handle, hw_params ); + deviceInfo->maxInputChannels = snd_pcm_hw_params_get_channels_max( hw_params ); + /* TODO: I'm not really sure what to do here */ + //deviceInfo->defaultLowInputLatency = snd_pcm_hw_params_get_period_size_min( hw_params, &dir ); + //deviceInfo->defaultHighInputLatency = snd_pcm_hw_params_get_period_size_max( hw_params, &dir ); + deviceInfo->defaultLowInputLatency = 128. / 44100; + deviceInfo->defaultHighInputLatency = 16384. / 44100; + snd_pcm_close( pcm_handle ); + } + + /* get max channels for playback */ + if( snd_pcm_open( &pcm_handle, alsaDeviceName, SND_PCM_STREAM_PLAYBACK, 0 ) < 0 ) + { + deviceInfo->maxOutputChannels = 0; + } + else + { + snd_pcm_hw_params_any( pcm_handle, hw_params ); + deviceInfo->maxOutputChannels = snd_pcm_hw_params_get_channels_max( hw_params ); + /* TODO: I'm not really sure what to do here */ + //deviceInfo->defaultLowOutputLatency = snd_pcm_hw_params_get_period_size_min( hw_params, &dir ); + //deviceInfo->defaultHighOutputLatency = snd_pcm_hw_params_get_period_size_max( hw_params, &dir ); + deviceInfo->defaultLowOutputLatency = 128. / 44100; + deviceInfo->defaultHighOutputLatency = 16384. / 44100; + snd_pcm_close( pcm_handle ); + } + + snd_pcm_hw_params_free( hw_params ); + } + + deviceInfo->defaultSampleRate = 44100.; /* IMPLEMENT ME */ + } + + commonApi->info.deviceCount = deviceCount; + commonApi->info.defaultInputDevice = 0; + commonApi->info.defaultOutputDevice = 0; + + return paNoError; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaAlsaHostApiRepresentation *skeletonHostApi; + skeletonHostApi = (PaAlsaHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resourced not handled by the allocation group + */ + + if( skeletonHostApi->allocations ) + { + PaUtil_FreeAllAllocations( skeletonHostApi->allocations ); + PaUtil_DestroyAllocationGroup( skeletonHostApi->allocations ); + } + + PaUtil_FreeMemory( skeletonHostApi ); +} + + +/* Given an open stream, what sample formats are available? */ + +static PaSampleFormat GetAvailableFormats( snd_pcm_t *stream ) +{ + PaSampleFormat available = 0; + snd_pcm_hw_params_t *hw_params; + snd_pcm_hw_params_alloca( &hw_params ); + + snd_pcm_hw_params_any( stream, hw_params ); + + if( snd_pcm_hw_params_test_format( stream, hw_params, SND_PCM_FORMAT_FLOAT ) == 0) + available |= paFloat32; + + if( snd_pcm_hw_params_test_format( stream, hw_params, SND_PCM_FORMAT_S16 ) == 0) + available |= paInt16; + + if( snd_pcm_hw_params_test_format( stream, hw_params, SND_PCM_FORMAT_S24 ) == 0) + available |= paInt24; + + if( snd_pcm_hw_params_test_format( stream, hw_params, SND_PCM_FORMAT_S32 ) == 0) + available |= paInt32; + + if( snd_pcm_hw_params_test_format( stream, hw_params, SND_PCM_FORMAT_S8 ) == 0) + available |= paInt8; + + if( snd_pcm_hw_params_test_format( stream, hw_params, SND_PCM_FORMAT_U8 ) == 0) + available |= paUInt8; + + return available; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError ConfigureStream( snd_pcm_t *stream, int channels, + int interleaved, unsigned long rate, + PaSampleFormat pa_format, int framesPerBuffer ) +{ +#define ENSURE(functioncall) \ + if( (functioncall) < 0 ) { \ + printf("Error executing ALSA call, line %d\n", __LINE__); \ + return 1; \ + } \ + else { \ + printf("ALSA call at line %d succeeded\n", __LINE__ ); \ + } + + snd_pcm_access_t access_mode; + snd_pcm_format_t alsa_format; + + /* configuration consists of setting all of ALSA's parameters. + * These parameters come in two flavors: hardware parameters + * and software paramters. Hardware parameters will affect + * the way the device is initialized, software parameters + * affect the way ALSA interacts with me, the user-level client. */ + + snd_pcm_hw_params_t *hw_params; + snd_pcm_sw_params_t *sw_params; + + snd_pcm_hw_params_alloca( &hw_params ); + + /* ... fill up the configuration space with all possibile + * combinations of parameters this device will accept */ + ENSURE( snd_pcm_hw_params_any( stream, hw_params ) ); + + if( interleaved ) + access_mode = SND_PCM_ACCESS_MMAP_INTERLEAVED; + else + access_mode = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + + ENSURE( snd_pcm_hw_params_set_access( stream, hw_params, access_mode ) ); + + /* set the format based on what the user selected */ + switch( pa_format ) + { + case paFloat32: + alsa_format = SND_PCM_FORMAT_FLOAT; + break; + + case paInt16: + alsa_format = SND_PCM_FORMAT_S16; + break; + + case paInt24: + alsa_format = SND_PCM_FORMAT_S24; + break; + + case paInt32: + alsa_format = SND_PCM_FORMAT_S32; + break; + + case paInt8: + alsa_format = SND_PCM_FORMAT_S8; + break; + + case paUInt8: + alsa_format = SND_PCM_FORMAT_U8; + break; + + default: + printf("Unknown PortAudio format %d\n", (int)pa_format ); + return 1; + } + //printf("PortAudio format: %d\n", pa_format); + printf("ALSA format: %d\n", alsa_format); + ENSURE( snd_pcm_hw_params_set_format( stream, hw_params, alsa_format ) ); + + /* ... set the sample rate */ + ENSURE( snd_pcm_hw_params_set_rate( stream, hw_params, rate, 0 ) ); + + /* ... set the number of channels */ + ENSURE( snd_pcm_hw_params_set_channels( stream, hw_params, channels ) ); + + /* ... set the number of periods to 2, which is essentially double buffering. + * this makes the latency the number of samples per buffer, which is the best + * it can be */ + ENSURE( snd_pcm_hw_params_set_periods ( stream, hw_params, 2, 0 ) ); + + /* ... set the period size, which is essentially the hardware buffer size */ + if( framesPerBuffer != 0 ) + { + ENSURE( snd_pcm_hw_params_set_period_size( stream, hw_params, + framesPerBuffer, 0 ) ); + } + else + { + ENSURE( snd_pcm_hw_params_set_period_size( stream, hw_params, + 2048, 0 ) ); + } + + + /* Set the parameters! */ + ENSURE( snd_pcm_hw_params( stream, hw_params ) ); + + return 0; +#undef ENSURE +} + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *callback, + void *userData ) +{ + PaError result = paNoError; + PaAlsaHostApiRepresentation *skeletonHostApi = + (PaAlsaHostApiRepresentation*)hostApi; + PaAlsaStream *stream = 0; + PaSampleFormat hostInputSampleFormat=0, hostOutputSampleFormat=0; + int numInputChannels, numOutputChannels; + PaSampleFormat inputSampleFormat=0, outputSampleFormat=0; + unsigned long framesPerHostBuffer = framesPerBuffer; + + if( framesPerHostBuffer == paFramesPerBufferUnspecified ) + { + // TODO: have some reason + framesPerHostBuffer = 2048; + } + + if( inputParameters ) + { + numInputChannels = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification. + [JH] this could be supported in the future, to allow ALSA device strings + like hw:0 */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that input device can support numInputChannels */ + if( numInputChannels > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + numInputChannels = 0; + } + + if( outputParameters ) + { + numOutputChannels = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification + [JH] this could be supported in the future, to allow ALSA device strings + like hw:0 */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + /* check that output device can support numInputChannels */ + if( numOutputChannels > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + numOutputChannels = 0; + } + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + /* allocate and do basic initialization of the stream structure */ + + stream = (PaAlsaStream*)PaUtil_AllocateMemory( sizeof(PaAlsaStream) ); + if( !stream ) + { + printf("memory point 2\n"); + result = paInsufficientMemory; + goto error; + } + + stream->pcm_capture = NULL; + stream->pcm_playback = NULL; + stream->callback_mode = (callback != 0); + stream->callback_finished = 0; + + if( callback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &skeletonHostApi->callbackStreamInterface, + callback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &skeletonHostApi->blockingStreamInterface, + callback, userData ); + } + + + stream->streamRepresentation.streamInfo.inputLatency = framesPerHostBuffer; + stream->streamRepresentation.streamInfo.outputLatency = framesPerHostBuffer; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + /* open the devices now, so we can obtain info about the available formats */ + + if( numInputChannels > 0 ) + { + char inputDeviceName[50]; + + sprintf( inputDeviceName, "hw:CARD=%s", hostApi->deviceInfos[inputParameters->device]->name ); + if( snd_pcm_open( &stream->pcm_capture, inputDeviceName, SND_PCM_STREAM_CAPTURE, 0 ) < 0 ) + { + result = paBadIODeviceCombination; + goto error; + } + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( GetAvailableFormats(stream->pcm_capture), + inputSampleFormat ); + } + + if( numOutputChannels > 0 ) + { + char outputDeviceName[50]; + + sprintf( outputDeviceName, "hw:CARD=%s", hostApi->deviceInfos[outputParameters->device]->name ); + if( snd_pcm_open( &stream->pcm_playback, outputDeviceName, SND_PCM_STREAM_PLAYBACK, 0 ) < 0 ) + { + result = paBadIODeviceCombination; + goto error; + } + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( GetAvailableFormats(stream->pcm_playback), + outputSampleFormat ); + stream->playback_hostsampleformat = hostOutputSampleFormat; + } + + + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + numInputChannels, inputSampleFormat, hostInputSampleFormat, + numOutputChannels, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, framesPerHostBuffer, + paUtilFixedHostBufferSize, callback, userData ); + if( result != paNoError ) + goto error; + + /* configure the streams */ + + if( numInputChannels > 0 ) + { + int interleaved; + PaSampleFormat plain_format = hostInputSampleFormat & ~paNonInterleaved; + + if( inputSampleFormat & paNonInterleaved ) + interleaved = 0; + else + interleaved = 1; + + if( ConfigureStream( stream->pcm_capture, numInputChannels, interleaved, + sampleRate, plain_format, framesPerHostBuffer ) != 0 ) + { + result = paBadIODeviceCombination; + goto error; + } + + stream->capture_interleaved = interleaved; + } + + if( numOutputChannels > 0 ) + { + int interleaved; + PaSampleFormat plain_format = hostOutputSampleFormat & ~paNonInterleaved; + + if( outputSampleFormat & paNonInterleaved ) + interleaved = 0; + else + interleaved = 1; + + if( ConfigureStream( stream->pcm_playback, numOutputChannels, interleaved, + sampleRate, plain_format, framesPerHostBuffer ) != 0 ) + { + result = paBadIODeviceCombination; + goto error; + } + + stream->playback_interleaved = interleaved; + } + + stream->capture_nfds = 0; + stream->playback_nfds = 0; + + if( stream->pcm_capture ) + stream->capture_nfds = snd_pcm_poll_descriptors_count( stream->pcm_capture ); + + if( stream->pcm_playback ) + stream->playback_nfds = snd_pcm_poll_descriptors_count( stream->pcm_playback ); + + /* TODO: free this properly */ + printf("trying to allocate %d bytes of memory\n", (stream->capture_nfds + stream->playback_nfds + 1) * sizeof(struct pollfd) ); + stream->pfds = (struct pollfd*)PaUtil_AllocateMemory( (stream->capture_nfds + + stream->playback_nfds + 1) * + sizeof(struct pollfd) ); + if( !stream->pfds ) + { + printf("bad memory point 1\n"); + result = paInsufficientMemory; + goto error; + } + + stream->frames_per_period = framesPerHostBuffer; + stream->capture_channels = numInputChannels; + stream->playback_channels = numOutputChannels; + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + PaUtil_FreeMemory( stream ); + + return result; +} + + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + + if( stream->pcm_capture ) + { + snd_pcm_close( stream->pcm_capture ); + } + + if( stream->pcm_playback ) + { + snd_pcm_close( stream->pcm_playback ); + } + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + + return result; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + + /* TODO: support errorText */ +#define ENSURE(x) \ + { \ + int error_ret; \ + error_ret = (x); \ + if( error_ret != 0 ) { \ + PaHostErrorInfo err; \ + err.errorCode = error_ret; \ + err.hostApiType = paALSA; \ + printf("call at %d failed\n", __LINE__); \ + return paUnanticipatedHostError; \ + } \ + else \ + printf("call at line %d succeeded\n", __LINE__); \ + } + + if( stream->pcm_capture ) + { + ENSURE( snd_pcm_prepare( stream->pcm_capture ) ); + } + + if( stream->pcm_playback ) + { + const snd_pcm_channel_area_t *playback_areas, *area; + snd_pcm_uframes_t offset, frames; + int sample_size = Pa_GetSampleSize( stream->playback_hostsampleformat ); + printf("Sample size: %d\n", sample_size ); + ENSURE( snd_pcm_prepare( stream->pcm_playback ) ); + frames = snd_pcm_avail_update( stream->pcm_playback ); + printf("frames: %d\n", (int)frames ); + printf("channels: %d\n", stream->playback_channels ); + + snd_pcm_mmap_begin( stream->pcm_playback, &playback_areas, &offset, &frames ); + + /* Insert silence */ + if( stream->playback_interleaved ) + { + void *playback_buffer; + area = &playback_areas[0]; + playback_buffer = area->addr + (area->first + area->step * offset) / 8; + memset( playback_buffer, 0, + frames * stream->playback_channels * sample_size ); + } + else + { + int i; + for( i = 0; i < stream->playback_channels; i++ ) + { + void *channel_buffer; + area = &playback_areas[i]; + channel_buffer = area->addr + (area->first + area->step * offset) / 8; + memset( channel_buffer, 0, frames * sample_size ); + } + } + + snd_pcm_mmap_commit( stream->pcm_playback, offset, frames ); + } + + if( stream->callback_mode ) + { + ENSURE( pthread_create( &stream->callback_thread, NULL, &CallbackThread, stream ) ); + + /* we'll do the snd_pcm_start() in the callback thread */ + } + else + { + if( stream->pcm_capture ) + snd_pcm_start( stream->pcm_capture ); + if( stream->pcm_playback ) + snd_pcm_start( stream->pcm_playback ); + } + + /* On my machine, the pcm stream will not transition to the RUNNING + * state for a while after snd_pcm_start is called. The PortAudio + * client needs to be able to depend on Pa_IsStreamActive() returning + * true the second after this function returns. So I sleep briefly here. + * + * I don't like this one bit. + */ + Pa_Sleep( 100 ); + + stream->callback_finished = 0; + + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + + /* First deal with the callback thread, cancelling and/or joining + * it if necessary + */ + + if( stream->callback_mode && stream->callback_finished ) + { + /* We are running in callback mode but the callback thread has + * already been cancelled by the return value from the user's + * callback function. Therefore we don't need to cancel the + * thread, but we do want to wait for it. */ + pthread_join( stream->callback_thread, NULL ); + } + else if( stream->callback_mode ) + { + /* We are running in callback mode, and the callback thread + * is still running. Cancel it and wait for it to be done. */ + pthread_cancel( stream->callback_thread ); + pthread_join( stream->callback_thread, NULL ); + } + + /* Stop the ALSA streams if necessary */ + + if( stream->callback_mode && stream->callback_finished ) + { + /* If we are in the callback_finished state the callback thread + * already stopped the streams. So there is nothing to do here. + */ + } + else + { + if( stream->pcm_capture ) + { + snd_pcm_drain( stream->pcm_capture ); + } + + if( stream->pcm_playback ) + { + snd_pcm_drain( stream->pcm_playback ); + } + } + + stream->callback_finished = 0; + + return result; +} + + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaAlsaStream *stream = (PaAlsaStream*)s; + + /* First deal with the callback thread, cancelling and/or joining + * it if necessary + */ + + if( stream->callback_mode && stream->callback_finished ) + { + /* We are running in callback mode but the callback thread has + * already been cancelled by the return value from the user's + * callback function. Therefore we don't need to cancel the + * thread, but we do want to wait for it. */ + pthread_join( stream->callback_thread, NULL ); + } + else if( stream->callback_mode ) + { + /* We are running in callback mode, and the callback thread + * is still running. Cancel it and wait for it to be done. */ + pthread_cancel( stream->callback_thread ); + pthread_join( stream->callback_thread, NULL ); + } + + /* Stop the ALSA streams if necessary */ + + if( stream->callback_mode && stream->callback_finished ) + { + /* If we are in the callback_finished state the callback thread + * already stopped the streams. So there is nothing to do here. + */ + } + else + { + if( stream->pcm_capture ) + { + snd_pcm_drop( stream->pcm_capture ); + } + + if( stream->pcm_playback ) + { + snd_pcm_drop( stream->pcm_playback ); + } + } + + stream->callback_finished = 0; + + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + if( IsStreamActive(s) || stream->callback_finished ) + return 0; + else + return 1; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + if( stream->pcm_capture ) + { + snd_pcm_state_t capture_state = snd_pcm_state( stream->pcm_capture ); + + if( capture_state == SND_PCM_STATE_RUNNING /*|| + capture_state == SND_PCM_STATE_PREPARED*/ ) + return 1; + } + + if( stream->pcm_playback ) + { + snd_pcm_state_t playback_state = snd_pcm_state( stream->pcm_playback ); + + if( playback_state == SND_PCM_STATE_RUNNING /*|| + playback_state == SND_PCM_STATE_PREPARED*/ ) + return 1; + } + + return 0; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + snd_output_t *output; + snd_timestamp_t timestamp; + snd_pcm_status_t *status; + snd_pcm_status_alloca( &status ); + + /* TODO: what if we have both? does it really matter? */ + + /* TODO: if running in callback mode, this will mean + * libasound routines are being called form multiple threads. + * need to verify that libasound is thread-safe. */ + + if( stream->pcm_capture ) + { + snd_pcm_status( stream->pcm_capture, status ); + } + else if( stream->pcm_playback ) + { + snd_pcm_status( stream->pcm_playback, status ); + } + + snd_pcm_status_get_tstamp( status, ×tamp ); + + return timestamp.tv_sec + ((float)timestamp.tv_usec/1000000); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + |