diff options
Diffstat (limited to 'pd/portaudio/pa_linux_alsa')
-rw-r--r-- | pd/portaudio/pa_linux_alsa/blocking_calls.c | 61 | ||||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/blocking_calls.o | bin | 0 -> 1180 bytes | |||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/callback_thread.c | 374 | ||||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/callback_thread.o | bin | 0 -> 4180 bytes | |||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/pa_linux_alsa.c | 989 | ||||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/pa_linux_alsa.h | 45 | ||||
-rw-r--r-- | pd/portaudio/pa_linux_alsa/pa_linux_alsa.o | bin | 0 -> 10728 bytes |
7 files changed, 1469 insertions, 0 deletions
diff --git a/pd/portaudio/pa_linux_alsa/blocking_calls.c b/pd/portaudio/pa_linux_alsa/blocking_calls.c new file mode 100644 index 00000000..6304b117 --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/blocking_calls.c @@ -0,0 +1,61 @@ + +#include "pa_stream.h" + +#include "pa_linux_alsa.h" + +PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + /* TODO: handle failure, xruns */ + + if( stream->capture_interleaved ) + { + snd_pcm_mmap_readi( stream->pcm_capture, buffer, frames ); + } + else + { + snd_pcm_mmap_readn( stream->pcm_capture, (void**)buffer, frames ); + } + + return paNoError; +} + + +PaError WriteStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + if( stream->playback_interleaved ) + { + snd_pcm_mmap_writei( stream->pcm_playback, buffer, frames ); + } + else + { + snd_pcm_mmap_writen( stream->pcm_playback, (void**)buffer, frames ); + } + + return paNoError; +} + + +unsigned long GetStreamReadAvailable( PaStream* s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + return snd_pcm_avail_update( stream->pcm_capture ); +} + + +unsigned long GetStreamWriteAvailable( PaStream* s ) +{ + PaAlsaStream *stream = (PaAlsaStream*)s; + + return snd_pcm_avail_update( stream->pcm_playback ); +} + + diff --git a/pd/portaudio/pa_linux_alsa/blocking_calls.o b/pd/portaudio/pa_linux_alsa/blocking_calls.o Binary files differnew file mode 100644 index 00000000..d4bc2108 --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/blocking_calls.o diff --git a/pd/portaudio/pa_linux_alsa/callback_thread.c b/pd/portaudio/pa_linux_alsa/callback_thread.c new file mode 100644 index 00000000..483557b6 --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/callback_thread.c @@ -0,0 +1,374 @@ + +#include <sys/poll.h> +#include <limits.h> +#include <math.h> /* abs() */ + +#include <alsa/asoundlib.h> + +#include "pa_linux_alsa.h" + +#define MIN(x,y) ( (x) < (y) ? (x) : (y) ) + +static int wait( PaAlsaStream *stream ) +{ + int need_capture; + int need_playback; + int capture_avail = INT_MAX; + int playback_avail = INT_MAX; + int common_avail; + + if( stream->pcm_capture ) + need_capture = 1; + else + need_capture = 0; + + if( stream->pcm_playback ) + need_playback = 1; + else + need_playback = 0; + + while( need_capture || need_playback ) + { + int playback_pfd_offset=0; + int total_fds = 0; + + /* if the main thread has requested that we stop, do so now */ + pthread_testcancel(); + + /*printf("still polling...\n"); + if( need_capture ) + printf("need capture.\n"); + if( need_playback ) + printf("need playback.\n"); */ + + /* get the fds, packing all applicable fds into a single array, + * so we can check them all with a single poll() call */ + + if( need_capture ) + { + snd_pcm_poll_descriptors( stream->pcm_capture, stream->pfds, + stream->capture_nfds ); + total_fds += stream->capture_nfds; + } + + if( need_playback ) + { + playback_pfd_offset = total_fds; + snd_pcm_poll_descriptors( stream->pcm_playback, + stream->pfds + playback_pfd_offset, + stream->playback_nfds ); + total_fds += stream->playback_nfds; + } + + /* now poll on the combination of playback and capture fds. + * TODO: handle interrupt and/or failure */ + poll( stream->pfds, total_fds, 1000 ); + + /* check the return status of our pfds */ + if( need_capture ) + { + short revents; + snd_pcm_poll_descriptors_revents( stream->pcm_capture, stream->pfds, + stream->capture_nfds, &revents ); + if( revents == POLLIN ) + need_capture = 0; + } + + if( need_playback ) + { + short revents; + snd_pcm_poll_descriptors_revents( stream->pcm_playback, + stream->pfds + playback_pfd_offset, + stream->playback_nfds, &revents ); + //if( revents & POLLOUT ) + //if( revents & POLLERR ) + // printf("polling error!"); + if( revents == POLLOUT ) + need_playback = 0; + } + } + + /* we have now established that there are buffers ready to be + * operated on. Now determine how many frames are available. */ + if( stream->pcm_capture ) + capture_avail = snd_pcm_avail_update( stream->pcm_capture ); + + if( stream->pcm_playback ) + playback_avail = snd_pcm_avail_update( stream->pcm_playback ); + + common_avail = MIN(capture_avail, playback_avail); + common_avail -= common_avail % stream->frames_per_period; + + return common_avail; +} + +static int setup_buffers( PaAlsaStream *stream, int frames_avail ) +{ + int i; + int capture_frames_avail = INT_MAX; + int playback_frames_avail = INT_MAX; + int common_frames_avail; + + if( stream->pcm_capture ) + { + const snd_pcm_channel_area_t *capture_areas; + const snd_pcm_channel_area_t *area; + snd_pcm_uframes_t frames = frames_avail; + + /* I do not understand this code fragment yet, it is copied out of the + * alsa-devel archives... */ + snd_pcm_mmap_begin( stream->pcm_capture, &capture_areas, + &stream->capture_offset, &frames); + + if( stream->capture_interleaved ) + { + void *interleaved_capture_buffer; + area = &capture_areas[0]; + interleaved_capture_buffer = area->addr + + (area->first + area->step * stream->capture_offset) / 8; + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, + 0, /* starting at channel 0 */ + interleaved_capture_buffer, + 0 /* default numInputChannels */ + ); + } + else + { + /* noninterleaved */ + void *noninterleaved_capture_buffers[1000]; + for( i = 0; i < stream->capture_channels; i++ ) + { + area = &capture_areas[i]; + noninterleaved_capture_buffers[i] = area->addr + + (area->first + area->step * stream->capture_offset) / 8; + PaUtil_SetNonInterleavedInputChannel( &stream->bufferProcessor, + i, + noninterleaved_capture_buffers[i]); + } + } + + capture_frames_avail = frames; + } + + if( stream->pcm_playback ) + { + const snd_pcm_channel_area_t *playback_areas; + const snd_pcm_channel_area_t *area; + snd_pcm_uframes_t frames = frames_avail; + + /* I do not understand this code fragment yet, it is copied out of the + * alsa-devel archives... */ + snd_pcm_mmap_begin( stream->pcm_playback, &playback_areas, + &stream->playback_offset, &frames); + + if( stream->playback_interleaved ) + { + void *interleaved_playback_buffer; + area = &playback_areas[0]; + interleaved_playback_buffer = area->addr + + (area->first + area->step * stream->playback_offset) / 8; + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, + 0, /* starting at channel 0 */ + interleaved_playback_buffer, + 0 /* default numInputChannels */ + ); + } + else + { + /* noninterleaved */ + void *noninterleaved_playback_buffers[1000]; + for( i = 0; i < stream->playback_channels; i++ ) + { + area = &playback_areas[i]; + noninterleaved_playback_buffers[i] = area->addr + + (area->first + area->step * stream->playback_offset) / 8; + PaUtil_SetNonInterleavedOutputChannel( &stream->bufferProcessor, + i, + noninterleaved_playback_buffers[i]); + } + } + + playback_frames_avail = frames; + } + + + common_frames_avail = MIN(capture_frames_avail, playback_frames_avail); + common_frames_avail -= common_frames_avail % stream->frames_per_period; + //printf( "%d capture frames available\n", capture_frames_avail ); + //printf( "%d frames playback available\n", playback_frames_avail ); + //printf( "%d frames available\n", common_frames_avail ); + + if( stream->pcm_capture ) + PaUtil_SetInputFrameCount( &stream->bufferProcessor, common_frames_avail ); + + if( stream->pcm_playback ) + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, common_frames_avail ); + + return common_frames_avail; +} + +void *CallbackThread( void *userData ) +{ + PaAlsaStream *stream = (PaAlsaStream*)userData; + + if( stream->pcm_capture ) + snd_pcm_start( stream->pcm_capture ); + if( stream->pcm_playback ) + snd_pcm_start( stream->pcm_playback ); + + while(1) + { + int frames_avail; + int frames_got; + + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* IMPLEMENT ME */ + int callbackResult; + int framesProcessed; + + pthread_testcancel(); + { + /* calculate time info */ + snd_timestamp_t capture_timestamp; + snd_timestamp_t playback_timestamp; + snd_pcm_status_t *capture_status; + snd_pcm_status_t *playback_status; + snd_pcm_status_alloca( &capture_status ); + snd_pcm_status_alloca( &playback_status ); + + if( stream->pcm_capture ) + { + snd_pcm_status( stream->pcm_capture, capture_status ); + snd_pcm_status_get_tstamp( capture_status, &capture_timestamp ); + } + if( stream->pcm_playback ) + { + snd_pcm_status( stream->pcm_playback, playback_status ); + snd_pcm_status_get_tstamp( playback_status, &playback_timestamp ); + } + + /* Hmm, we potentially have both a playback and a capture timestamp. + * Hopefully they are the same... */ + if( stream->pcm_capture && stream->pcm_playback ) + { + float capture_time = capture_timestamp.tv_sec + + ((float)capture_timestamp.tv_usec/1000000); + float playback_time= playback_timestamp.tv_sec + + ((float)playback_timestamp.tv_usec/1000000); + if( fabsf(capture_time-playback_time) > 0.01 ) + printf("Capture time and playback time differ by %f\n", fabsf(capture_time-playback_time)); + timeInfo.currentTime = capture_time; + } + else if( stream->pcm_playback ) + { + timeInfo.currentTime = playback_timestamp.tv_sec + + ((float)playback_timestamp.tv_usec/1000000); + } + else + { + timeInfo.currentTime = capture_timestamp.tv_sec + + ((float)capture_timestamp.tv_usec/1000000); + } + + if( stream->pcm_capture ) + { + snd_pcm_sframes_t capture_delay = snd_pcm_status_get_delay( capture_status ); + timeInfo.inputBufferAdcTime = timeInfo.currentTime - + (float)capture_delay / stream->streamRepresentation.streamInfo.sampleRate; + } + + if( stream->pcm_playback ) + { + snd_pcm_sframes_t playback_delay = snd_pcm_status_get_delay( playback_status ); + timeInfo.outputBufferDacTime = timeInfo.currentTime + + (float)playback_delay / stream->streamRepresentation.streamInfo.sampleRate; + } + } + + + /* + IMPLEMENT ME: + - handle buffer slips + */ + + /* + depending on whether the host buffers are interleaved, non-interleaved + or a mixture, you will want to call PaUtil_ProcessInterleavedBuffers(), + PaUtil_ProcessNonInterleavedBuffers() or PaUtil_ProcessBuffers() here. + */ + + frames_avail = wait( stream ); + //printf( "%d frames available\n", frames_avail ); + + /* Now we know the soundcard is ready to produce/receive at least + * one period. We just need to get the buffers for the client + * to read/write. */ + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo ); + + frames_got = setup_buffers( stream, frames_avail ); + + if( frames_avail == frames_got ) + ;//printf("good, they were both %d\n", frames_avail ); + else + printf("damn, they were different: avail: %d, got: %d\n", frames_avail, frames_got ); + + /* this calls the callback */ + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &callbackResult ); + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); + + /* inform ALSA how many frames we wrote */ + + if( stream->pcm_capture ) + snd_pcm_mmap_commit( stream->pcm_capture, stream->capture_offset, frames_avail ); + + if( stream->pcm_playback ) + snd_pcm_mmap_commit( stream->pcm_playback, stream->playback_offset, frames_avail ); + + + /* + If you need to byte swap outputBuffer, you can do it here using + routines in pa_byteswappers.h + */ + + if( callbackResult == paContinue ) + { + /* nothing special to do */ + } + else if( callbackResult == paAbort ) + { + stream->callback_finished = 1; + + if( stream->pcm_capture ) + { + snd_pcm_drop( stream->pcm_capture ); + } + + if( stream->pcm_playback ) + { + snd_pcm_drop( stream->pcm_playback ); + } + pthread_exit(NULL); + } + else + { + stream->callback_finished = 1; + + if( stream->pcm_capture ) + { + snd_pcm_drain( stream->pcm_capture ); + } + + if( stream->pcm_playback ) + { + snd_pcm_drain( stream->pcm_playback ); + } + pthread_exit(NULL); + } + + } +} + diff --git a/pd/portaudio/pa_linux_alsa/callback_thread.o b/pd/portaudio/pa_linux_alsa/callback_thread.o Binary files differnew file mode 100644 index 00000000..a242d490 --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/callback_thread.o 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 ); +} + diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h new file mode 100644 index 00000000..62c9512c --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.h @@ -0,0 +1,45 @@ + +#include <alsa/asoundlib.h> + +#include <pthread.h> + +#include "pa_util.h" +#include "pa_process.h" +#include "pa_cpuload.h" +#include "pa_stream.h" + +typedef struct PaAlsaStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + snd_pcm_t *pcm_capture; + snd_pcm_t *pcm_playback; + + int callback_finished; /* bool: are we in the "callback finished" state? */ + + int frames_per_period; + int playback_hostsampleformat; + + int capture_channels; + int playback_channels; + + int capture_interleaved; /* bool: is capture interleaved? */ + int playback_interleaved; /* bool: is playback interleaved? */ + + int callback_mode; /* bool: are we running in callback mode? */ + pthread_t callback_thread; + + /* the callback thread uses these to poll the sound device, waiting + * for data to be ready/available */ + unsigned int capture_nfds; + unsigned int playback_nfds; + struct pollfd *pfds; + + /* these aren't really stream state, the callback uses them */ + snd_pcm_uframes_t capture_offset; + snd_pcm_uframes_t playback_offset; +} +PaAlsaStream; + diff --git a/pd/portaudio/pa_linux_alsa/pa_linux_alsa.o b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.o Binary files differnew file mode 100644 index 00000000..f79af61d --- /dev/null +++ b/pd/portaudio/pa_linux_alsa/pa_linux_alsa.o |