$NetBSD: patch-src_hostapi_sun_pa__unix__sun.c,v 1.5 2022/06/14 14:18:45 nia Exp $ Sun/NetBSD audio support. This is upstreamed to GitHub. --- src/hostapi/sun/pa_unix_sun.c.orig 2022-06-14 14:03:33.619499935 +0000 +++ src/hostapi/sun/pa_unix_sun.c @@ -0,0 +1,1123 @@ +/* + * $Id" + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * Sun implementation by: + * Nia Alarie + * + * 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. + * + * 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. + */ + +/** @file + @ingroup hostapi_src +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "pa_util.h" +#include "pa_unix_util.h" +#include "pa_debugprint.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#ifndef SUN_MAX_DEVICES +#define SUN_MAX_DEVICES (32) +#endif + +#ifndef SUN_DEFAULT_FRAMES +#define SUN_DEFAULT_FRAMES (128) +#endif + +#ifndef SUN_DEFAULT_MAX_CHANNELS +# ifdef __NetBSD__ +# define SUN_DEFAULT_MAX_CHANNELS (12) +# else +# define SUN_DEFAULT_MAX_CHANNELS (2) +# endif +#endif + +#ifndef SUN_DEV_PREFIX +# ifdef __sun +# define SUN_DEV_PREFIX "/dev/sound/" +# else +# define SUN_DEV_PREFIX "/dev/audio" +# endif +#endif + +#ifndef SUN_DEV_DEFAULT +#define SUN_DEV_DEFAULT "/dev/audio" +#endif + +#ifndef AUDIO_ENCODING_SLINEAR +#define AUDIO_ENCODING_SLINEAR AUDIO_ENCODING_LINEAR +#endif + +#if 0 +/* testing */ +#undef AUDIO_GETPROPS +#undef AUDIO_GETFORMAT +#undef AUDIO_GETBUFINFO +#endif + +PaError PaSun_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static bool AttemptEncoding( int fd, int encoding, int precision, bool record ); +static PaSampleFormat GetSupportedEncodings( int fd, bool record ); +static PaError PaFormatToSunFormat( PaSampleFormat fmt, struct audio_prinfo *info ); +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); +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* 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 ReadStream( PaStream* stream, void *buffer, unsigned long frames ); +static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); +static signed long GetStreamReadAvailable( PaStream* stream ); +static signed long GetStreamWriteAvailable( PaStream* stream ); + +/* PaSunHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + PaUtilAllocationGroup *allocations; +} +PaSunHostApiRepresentation; + + +PaError PaSun_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int fd = -1, i, deviceCount = 0; + PaSunHostApiRepresentation *sunHostApi; + PaUtilHostApiRepresentation *common; + + PA_UNLESS( sunHostApi = calloc(1, sizeof(PaSunHostApiRepresentation)), paInsufficientMemory ); + + common = &sunHostApi->inheritedHostApiRep; + common->info.structVersion = 1; + common->info.type = paInDevelopment; + common->info.name = "Sun AudioIO"; + + common->Terminate = Terminate; + common->OpenStream = OpenStream; + common->IsFormatSupported = IsFormatSupported; + + common->info.defaultInputDevice = + common->info.defaultOutputDevice = paNoDevice; + + PA_UNLESS( sunHostApi->allocations = PaUtil_CreateAllocationGroup(), paInsufficientMemory ); + + if( (common->deviceInfos = PaUtil_GroupAllocateMemory( + sunHostApi->allocations, sizeof(PaDeviceInfo *) * SUN_MAX_DEVICES)) == NULL ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < SUN_MAX_DEVICES; ++i ) + { + PaDeviceInfo *dev; + struct audio_info hwfmt, info; + char path[16]; + int props; + + if( i > 0 ) + (void)snprintf(path, sizeof(path), SUN_DEV_PREFIX "%d", i - 1); + else + (void)snprintf(path, sizeof(path), SUN_DEV_DEFAULT); + + fd = open(path, O_WRONLY | O_NONBLOCK); + if (fd < 0) + fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd < 0) + continue; + + PA_UNLESS(dev = PaUtil_GroupAllocateMemory(sunHostApi->allocations, sizeof(PaDeviceInfo)), paInsufficientMemory); + + dev->structVersion = 2; + dev->hostApi = hostApiIndex; + + PA_UNLESS(dev->name = PaUtil_GroupAllocateMemory(sunHostApi->allocations, sizeof(path)), paInsufficientMemory); + + memcpy((char *)dev->name, path, sizeof(path)); + + dev->defaultSampleRate = 48000.; + + dev->maxInputChannels = dev->maxOutputChannels = SUN_DEFAULT_MAX_CHANNELS; + + dev->defaultLowInputLatency = + dev->defaultLowOutputLatency = + dev->defaultHighInputLatency = + dev->defaultHighOutputLatency = .04; + +#ifdef AUDIO_GETPROPS + if( ioctl(fd, AUDIO_GETPROPS, &props) != -1 ) + { + if( (props & AUDIO_PROP_PLAYBACK) == 0 ) + dev->maxOutputChannels = 0; + if( (props & AUDIO_PROP_CAPTURE) == 0 ) + dev->maxInputChannels = 0; + } +#endif + +#ifdef AUDIO_GETFORMAT + if( ioctl(fd, AUDIO_GETFORMAT, &hwfmt) != -1 ) + { + if( dev->maxInputChannels > hwfmt.record.channels ) + dev->maxInputChannels = hwfmt.record.channels; + if( dev->maxOutputChannels > hwfmt.play.channels ) + dev->maxOutputChannels = hwfmt.play.channels; + dev->defaultSampleRate = (dev->maxOutputChannels > 0) ? + hwfmt.play.sample_rate : hwfmt.record.sample_rate; + } +#endif + +#ifdef AUDIO_GETBUFINFO + AUDIO_INITINFO(&info); + if( dev->maxOutputChannels > 0 ) + { + info.play.encoding = AUDIO_ENCODING_SLINEAR; + info.play.precision = 16; + info.play.sample_rate = dev->defaultSampleRate; + info.play.channels = dev->maxOutputChannels; + } + if( dev->maxInputChannels > 0 ) + { + info.record.encoding = AUDIO_ENCODING_SLINEAR; + info.record.precision = 16; + info.record.sample_rate = dev->defaultSampleRate; + info.record.channels = dev->maxInputChannels; + } + if( ioctl(fd, AUDIO_SETINFO, &info) != -1 && + ioctl(fd, AUDIO_GETBUFINFO, &info) != -1 ) + { + if( dev->maxOutputChannels > 0 ) + { + dev->defaultLowOutputLatency = + dev->defaultHighOutputLatency = + info.blocksize / (info.play.channels * (info.play.precision / 8)) / + (double)info.play.sample_rate; + } + if( dev->maxInputChannels > 0 ) + { + dev->defaultLowInputLatency = + dev->defaultHighInputLatency = + info.blocksize / (info.record.channels * (info.record.precision / 8)) / + (double)info.record.sample_rate; + } + } +#endif + + if( common->info.defaultInputDevice == paNoDevice && dev->maxInputChannels > 0 ) + common->info.defaultInputDevice = deviceCount; + + if( common->info.defaultOutputDevice == paNoDevice && dev->maxOutputChannels > 0 ) + common->info.defaultOutputDevice = deviceCount; + + common->deviceInfos[deviceCount++] = dev; + + close(fd); + fd = -1; + } + + common->info.deviceCount = deviceCount; + + *hostApi = common; + + PaUtil_InitializeStreamInterface( &sunHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &sunHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + return result; + +error: + if( fd != -1 ) + close(fd); + + if( sunHostApi ) + { + if( sunHostApi->allocations ) + { + PaUtil_FreeAllAllocations( sunHostApi->allocations ); + PaUtil_DestroyAllocationGroup( sunHostApi->allocations ); + } + free( sunHostApi ); + } + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaSunHostApiRepresentation *sunHostApi = (PaSunHostApiRepresentation*)hostApi; + + if( sunHostApi ) + { + if( sunHostApi->allocations ) + { + PaUtil_FreeAllAllocations( sunHostApi->allocations ); + PaUtil_DestroyAllocationGroup( sunHostApi->allocations ); + } + free( sunHostApi ); + } +} + + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaError result = paFormatIsSupported; + PaDeviceInfo *indev, *outdev; + struct audio_info info; + int fd = -1; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + indev = hostApi->deviceInfos[ inputParameters->device ]; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > indev->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + return paSampleFormatNotSupported; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + outdev = hostApi->deviceInfos[ outputParameters->device ]; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > outdev->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + } + + if( inputChannelCount > 0 && outputChannelCount > 0 ) + { + if( strcmp(indev->name, outdev->name) == 0 ) + { + PA_UNLESS( (fd = open(indev->name, O_RDWR | O_NONBLOCK)) != -1, paDeviceUnavailable ); + AUDIO_INITINFO(&info); + info.play.channels = outputChannelCount; + info.record.channels = inputChannelCount; + PA_UNLESS( ioctl(fd, AUDIO_SETINFO, &info) != -1, paInvalidChannelCount ); + AUDIO_INITINFO(&info); + info.play.sample_rate = sampleRate; + info.record.sample_rate = sampleRate; + PA_UNLESS( ioctl(fd, AUDIO_SETINFO, &info) != -1, paInvalidSampleRate ); + close(fd); + return result; + } + } + + if( inputChannelCount > 0 ) + { + PA_UNLESS( (fd = open(indev->name, O_RDONLY | O_NONBLOCK)) != -1, paDeviceUnavailable ); + AUDIO_INITINFO(&info); + info.record.channels = inputChannelCount; + PA_UNLESS( ioctl(fd, AUDIO_SETINFO, &info) != -1, paInvalidChannelCount ); + AUDIO_INITINFO(&info); + info.record.sample_rate = sampleRate; + PA_UNLESS( ioctl(fd, AUDIO_SETINFO, &info) != -1, paInvalidSampleRate ); + close(fd); + fd = -1; + } + + if( outputChannelCount > 0 ) + { + PA_UNLESS( (fd = open(outdev->name, O_WRONLY | O_NONBLOCK)) != -1, paDeviceUnavailable ); + AUDIO_INITINFO(&info); + info.play.channels = outputChannelCount; + PA_UNLESS( ioctl(fd, AUDIO_SETINFO, &info) != -1, paInvalidChannelCount ); + AUDIO_INITINFO(&info); + info.play.sample_rate = sampleRate; + PA_UNLESS( ioctl(fd, AUDIO_SETINFO, &info) != -1, paInvalidSampleRate ); + close(fd); + fd = -1; + } + +error: + if( result == paInvalidChannelCount ) + PA_DEBUG(("PaSun %s: Invalid channels %d in %d out\n", __FUNCTION__, inputChannelCount, outputChannelCount)); + if( result == paInvalidSampleRate ) + PA_DEBUG(("PaSun %s: Invalid sample rate %d Hz\n", __FUNCTION__, (int)sampleRate)); + if( fd != -1 ) + close(fd); + return result; +} + +typedef struct PaSunComponent +{ + int fd; + char name[16]; + void *chanbufs[16]; + void *buffer; + unsigned int bufferSize; /* in frames */ + unsigned int channels; + unsigned int frameSize; /* precision in bytes * channel count */ +} +PaSunComponent; + +typedef struct PaSunStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + PaUtilThreading threading; + PaSunComponent play, record; + bool active, stopped; + bool neverDropInput; + uint64_t framesProcessed; + uint64_t eof; + double sampleRate; +} +PaSunStream; + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +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; + PaSunHostApiRepresentation *sunHostApi = (PaSunHostApiRepresentation*)hostApi; + PaSunStream *stream = 0; + unsigned long framesPerHostBuffer = framesPerBuffer; /* these may not be equivalent for all implementations */ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + PaTime inputLatency = .04, outputLatency = .04; + PaDeviceInfo *indev, *outdev; + struct audio_info info; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + indev = hostApi->deviceInfos[ inputParameters->device ]; + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > indev->maxInputChannels ) + return paInvalidChannelCount; + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */ + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + return paInvalidDevice; + + outdev = hostApi->deviceInfos[ outputParameters->device ]; + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > outdev->maxOutputChannels ) + return paInvalidChannelCount; + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */ + } + + PA_UNLESS(stream = calloc( 1, sizeof(PaSunStream) ), paInsufficientMemory); + + stream->play.fd = stream->record.fd = -1; + stream->stopped = true; + stream->neverDropInput = streamFlags & paNeverDropInput; + + if( inputChannelCount > 0 && outputChannelCount > 0) + { + if( inputParameters->device == outputParameters->device ) + { + PA_DEBUG(("PaSun %s: Opening device %s in full duplex\n", __FUNCTION__, indev->name)); + PA_UNLESS( (stream->play.fd = open(indev->name, O_RDWR)) != -1, paDeviceUnavailable ); + stream->record.fd = stream->play.fd; + } + } + + if( inputChannelCount > 0 ) + { + if( stream->record.fd == -1 ) + PA_UNLESS((stream->record.fd = open(indev->name, O_RDONLY)) != -1, paDeviceUnavailable); + + memcpy(stream->record.name, indev->name, strlen(indev->name)); + + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( GetSupportedEncodings(stream->record.fd, true), inputSampleFormat ); + + AUDIO_INITINFO(&info); + if( PaFormatToSunFormat(hostInputSampleFormat, &info.record ) != paNoError || + ioctl(stream->record.fd, AUDIO_SETINFO, &info) < 0 ) + { + result = paSampleFormatNotSupported; + goto error; + } + + AUDIO_INITINFO(&info); + info.record.sample_rate = sampleRate; + PA_UNLESS( ioctl(stream->record.fd, AUDIO_SETINFO, &info) != -1, paInvalidSampleRate ); + + AUDIO_INITINFO(&info); + info.record.channels = inputChannelCount; + PA_UNLESS( ioctl(stream->record.fd, AUDIO_SETINFO, &info) != -1, paInvalidChannelCount ); + + PA_UNLESS( ioctl(stream->record.fd, AUDIO_GETINFO, &info) != -1, paDeviceUnavailable ); + + PA_DEBUG(("PaSun %s: %u-bit %u-channel recording stream at %u Hz\n", __FUNCTION__, + info.record.precision, info.record.channels, info.record.sample_rate)); + + sampleRate = info.record.sample_rate; + + stream->record.channels = inputChannelCount = info.record.channels; + stream->record.frameSize = (info.record.precision / 8) * info.record.channels; + stream->record.bufferSize = framesPerBuffer; + +#ifdef AUDIO_GETBUFINFO + inputLatency = (info.blocksize / stream->record.frameSize) + + PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor ) / sampleRate; +#else + inputLatency += PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor ) / sampleRate; +#endif + + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { +#ifdef AUDIO_GETBUFINFO + stream->record.bufferSize = info.blocksize / stream->record.frameSize; +#else + stream->record.bufferSize = SUN_DEFAULT_FRAMES; +#endif + framesPerBuffer = framesPerHostBuffer = stream->record.bufferSize; + } + + PA_UNLESS(stream->record.buffer = calloc(stream->record.bufferSize, stream->record.frameSize), paInsufficientMemory); + } + + if( outputChannelCount > 0 ) + { + if( stream->play.fd == -1 ) + PA_UNLESS((stream->play.fd = open(outdev->name, O_WRONLY)) != -1, paDeviceUnavailable); + + memcpy(stream->play.name, outdev->name, strlen(outdev->name)); + + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( GetSupportedEncodings(stream->play.fd, false), outputSampleFormat ); + + AUDIO_INITINFO(&info); + if( PaFormatToSunFormat(hostOutputSampleFormat, &info.play ) != paNoError || + ioctl(stream->play.fd, AUDIO_SETINFO, &info) < 0 ) + { + result = paSampleFormatNotSupported; + goto error; + } + + AUDIO_INITINFO(&info); + info.play.sample_rate = sampleRate; + PA_UNLESS( ioctl(stream->play.fd, AUDIO_SETINFO, &info) != -1, paInvalidSampleRate ); + + AUDIO_INITINFO(&info); + info.play.channels = outputChannelCount; + PA_UNLESS( ioctl(stream->play.fd, AUDIO_SETINFO, &info) != -1, paInvalidChannelCount ); + + PA_UNLESS( ioctl(stream->play.fd, AUDIO_GETINFO, &info) != -1, paDeviceUnavailable ); + + PA_DEBUG(("PaSun %s: %u-bit %u-channel playback stream at %u Hz\n", __FUNCTION__, + info.play.precision, info.play.channels, info.play.sample_rate)); + + sampleRate = info.play.sample_rate; + + stream->play.channels = outputChannelCount = info.play.channels; + stream->play.frameSize = (info.play.precision / 8) * info.play.channels; + stream->play.bufferSize = framesPerBuffer; + +#ifdef AUDIO_GETBUFINFO + outputLatency = (info.blocksize / stream->play.frameSize) + + PaUtil_GetBufferProcessorOutputLatencyFrames( &stream->bufferProcessor ) / sampleRate; +#else + outputLatency += PaUtil_GetBufferProcessorInputLatencyFrames( &stream->bufferProcessor ) / sampleRate; +#endif + + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { +#ifdef AUDIO_GETBUFINFO + stream->play.bufferSize = info.blocksize / stream->play.frameSize; +#else + stream->play.bufferSize = SUN_DEFAULT_FRAMES; +#endif + framesPerBuffer = framesPerHostBuffer = stream->play.bufferSize; + } + + PA_UNLESS( stream->play.buffer = calloc(stream->play.bufferSize, stream->play.frameSize), paInsufficientMemory ); + } + + stream->sampleRate = sampleRate; + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &sunHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &sunHostApi->blockingStreamInterface, streamCallback, userData ); + } + + PA_ENSURE( PaUtil_InitializeThreading( &stream->threading ) ); + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + /* we assume a fixed host buffer size in this example, but the buffer processor + can also support bounded and unknown host buffer sizes by passing + paUtilBoundedHostBufferSize or paUtilUnknownHostBufferSize instead of + paUtilFixedHostBufferSize below. */ + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerHostBuffer, paUtilFixedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) + goto error; + + stream->streamRepresentation.streamInfo.inputLatency = inputLatency; + stream->streamRepresentation.streamInfo.outputLatency = outputLatency; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + *s = (PaStream*)stream; + + return result; + +error: + if( stream != NULL ) + { + if( stream->play.fd != -1) + close( stream->play.fd ); + if( stream->record.fd != -1 && stream->record.fd != stream->play.fd ) + close( stream->record.fd ); + free( stream->play.buffer ); + free( stream->record.buffer ); + free( stream ); + } + return result; +} + +static void *PaSun_AudioThreadProc( void *userData ) +{ + PaError result = paNoError; + PaSunStream *stream = (PaSunStream*)userData; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; + int callbackResult; + struct audio_info info; + unsigned int pframes, rframes; + size_t off; + + while(!stream->stopped) + { + pframes = rframes = 0; + + if( stream->play.channels > 0 ) + pframes = stream->play.bufferSize; + + if( stream->record.channels > 0 ) + rframes = stream->record.bufferSize; + + if( rframes > 0 ) + { + ssize_t len = rframes * stream->record.frameSize; + PA_ENSURE( (len = read(stream->record.fd, stream->record.buffer, len)) != -1 ); + rframes = len / stream->record.frameSize; + if( pframes > rframes ) + pframes = rframes; + } + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, 0 ); + + if( rframes > 0 ) + { + PaUtil_SetInputFrameCount( &stream->bufferProcessor, rframes ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->record.buffer, stream->record.channels ); + } + else if ( stream->record.channels > 0 ) + { + PaUtil_SetNoInput( &stream->bufferProcessor ); + } + + if( pframes > 0 ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, pframes ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->play.buffer, stream->play.channels ); + } + else if ( stream->play.channels > 0 && stream->neverDropInput ) + { + PaUtil_SetNoOutput( &stream->bufferProcessor ); + } + + callbackResult = paContinue; + pframes = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, pframes ); + + stream->framesProcessed += pframes; + + off = 0; + + while( stream->play.channels > 0 && pframes > 0 ) + { + ssize_t len = pframes * stream->play.frameSize; + PA_ENSURE( (len = write(stream->play.fd, stream->play.buffer + off, len)) != -1 ); + pframes -= (len / stream->play.frameSize); + off += len; + } + + if( callbackResult != paContinue ) + break; + } + + if( callbackResult == paAbort ) + { + /* once finished, call the finished callback */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + else + { + /* once finished, call the finished callback */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + +error: + PaUtil_ResetCpuLoadMeasurer( &stream->cpuLoadMeasurer ); + stream->active = false; + PA_DEBUG(("PaSun %s: Thread exited\n", __FUNCTION__)); + pthread_exit( NULL ); +} + +/* + 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; + PaSunStream *stream = (PaSunStream*)s; + + PaUtil_TerminateThreading ( &stream->threading ); + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + if( stream->play.fd != -1 ) + close(stream->play.fd); + + if( stream->record.fd != -1 && stream->record.fd != stream->play.fd ) + close(stream->record.fd); + + free( stream->play.buffer ); + free( stream->record.buffer ); + free( stream ); + + return result; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaSunStream *stream = (PaSunStream*)s; + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + stream->active = true; + stream->stopped = false; + + if( stream->bufferProcessor.streamCallback ) + PA_ENSURE( PaUtil_StartThreading( &stream->threading, &PaSun_AudioThreadProc, stream ) ); + +error: + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaSunStream *stream = (PaSunStream*)s; + + stream->stopped = true; + + if( stream->bufferProcessor.streamCallback ) + PA_ENSURE( PaUtil_CancelThreading( &stream->threading, 1, NULL ) ); + + if( stream->play.fd != -1 ) + (void)ioctl(stream->play.fd, AUDIO_DRAIN); + +error: + return result; +} + +static PaError AbortStream( PaStream *s ) +{ + StopStream(s); +} + +static PaError IsStreamStopped( PaStream *s ) +{ + PaSunStream *stream = (PaSunStream*)s; + + return stream->stopped; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaSunStream *stream = (PaSunStream*)s; + + return stream->active; +} + + +static PaTime GetStreamTime( PaStream *s ) +{ + PaSunStream *stream = (PaSunStream*)s; + + return stream->framesProcessed / stream->sampleRate; +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaSunStream *stream = (PaSunStream*)s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaSunStream *stream = (PaSunStream*)s; + void *chanbufs = buffer; + unsigned wantframes, wantbytes; + + /* If user output is non-interleaved, PaUtil_CopyInput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( !stream->bufferProcessor.userInputIsInterleaved ) + { + /* Copy channels into local array */ + chanbufs = stream->play.chanbufs; + memcpy( chanbufs, buffer, sizeof (void *) * stream->record.channels ); + } + + while( frames ) + { + wantframes = PA_MIN( frames, stream->record.bufferSize ); + wantbytes = wantframes * stream->record.frameSize; + + if ( read(stream->record.fd, stream->record.buffer, wantbytes) < wantbytes ) + return paUnanticipatedHostError; + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, stream->record.bufferSize ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, stream->record.buffer, stream->record.channels ); + PaUtil_CopyInput( &stream->bufferProcessor, &chanbufs, wantframes ); + + frames -= wantframes; + } + + return paNoError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaSunStream *stream = (PaSunStream*)s; + const void *chanbufs = buffer; + unsigned nbytes; + unsigned long gotframes; + + /* If user output is non-interleaved, PaUtil_CopyOutput will manipulate the channel pointers, + * so we copy the user provided pointers */ + if( !stream->bufferProcessor.userOutputIsInterleaved ) + { + /* Copy channels into local array */ + chanbufs = stream->play.chanbufs; + memcpy( (void *)chanbufs, buffer, sizeof (void *) * stream->play.channels ); + } + + while( frames ) + { + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, stream->play.bufferSize ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, stream->play.buffer, stream->play.channels ); + + gotframes = PaUtil_CopyOutput( &stream->bufferProcessor, &chanbufs, frames ); + + nbytes = gotframes * stream->play.frameSize; + + if( write(stream->play.fd, stream->play.buffer, nbytes) < nbytes ) + return paUnanticipatedHostError; + + frames -= gotframes; + } + if( write(stream->play.fd, stream->play.buffer, 0) < 0 ) + return paUnanticipatedHostError; + stream->eof++; + return paNoError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaSunStream *stream = (PaSunStream*)s; +#ifdef AUDIO_GETBUFINFO + struct audio_info info; + + if( ioctl(stream->record.fd, AUDIO_GETBUFINFO, &info) == -1 ) + return paDeviceUnavailable; + + return info.record.seek / stream->record.frameSize; +#else + struct pollfd pfd[1]; + + memset(pfd, 0, sizeof(pfd)); + pfd[0].fd = stream->record.fd; + pfd[0].events = POLLIN; + + if( poll(pfd, 1, 0) == -1 ) + return paDeviceUnavailable; + + return (pfd[0].revents & POLLIN) ? SUN_DEFAULT_FRAMES : 0; +#endif +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaSunStream *stream = (PaSunStream*)s; + struct audio_info info; + +#ifdef AUDIO_GETBUFINFO + if( ioctl(stream->play.fd, AUDIO_GETBUFINFO, &info) == -1 ) + return paDeviceUnavailable; + + return ((info.hiwat * info.blocksize) - info.play.seek) / stream->play.frameSize; +#else + if( ioctl(stream->play.fd, AUDIO_GETINFO, &info) == -1 ) + return paDeviceUnavailable; + + if( (stream->eof - info.play.eof) > 2 ) + return 0; + + return SUN_DEFAULT_FRAMES; +#endif +} + +static PaError PaFormatToSunFormat( PaSampleFormat fmt, struct audio_prinfo *info ) +{ + PaError err = paNoError; + + switch( fmt ) + { + case paInt32: + info->encoding = AUDIO_ENCODING_SLINEAR; + info->precision = 32; + break; + case paInt16: + info->encoding = AUDIO_ENCODING_SLINEAR; + info->precision = 16; + break; + case paInt8: + info->encoding = AUDIO_ENCODING_SLINEAR; + info->precision = 8; + break; + default: + err = paSampleFormatNotSupported; + break; + } + return err; +} + +static bool AttemptEncoding( int fd, int encoding, int precision, bool record ) +{ + struct audio_info info; + struct audio_prinfo *prinfo = record ? &info.record : &info.play; + + AUDIO_INITINFO(&info); + prinfo->encoding = encoding; + prinfo->precision = precision; + + return ioctl(fd, AUDIO_SETINFO, &info) != -1; +} + +static PaSampleFormat GetSupportedEncodings( int fd, bool record ) +{ + PaSampleFormat fmts = 0; + + if( AttemptEncoding(fd, AUDIO_ENCODING_SLINEAR, 32, record) ) + fmts |= paInt32; + + if( AttemptEncoding(fd, AUDIO_ENCODING_SLINEAR, 16, record) ) + fmts |= paInt16; + + if( AttemptEncoding(fd, AUDIO_ENCODING_SLINEAR, 8, record) ) + fmts |= paInt8; + + return fmts; +}