
/*
 * PulseAudio host to play natively in Linux based systems without
 * ALSA emulation
 *
 * Copyright (c) 2014-2023 Tuukka Pasanen <tuukka.pasanen@ilmi.fi>
 * Copyright (c) 2016 Sqweek
 * Copyright (c) 2020 Daniel Schurmann
 *
 * 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 common_src

 @brief PulseAudio implementation of support for a host API.

 This host API implements PulseAudio support for portaudio
 it has callbackmode and normal write mode support
*/


#include "pa_linux_pulseaudio_cb_internal.h"
#include "pa_linux_pulseaudio_block_internal.h"

/* PulseAudio headers */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pulse/pulseaudio.h>

/* This is used to identify process name for PulseAudio. */
extern char *__progname;

/* PulseAudio specific functions */
int PaPulseAudio_CheckConnection( PaPulseAudio_HostApiRepresentation * ptr )
{
    /*
     * This is bit hackish as -1 is considered as an error
     * but PA_ERR_* are positive and PA_OK is zero.
     * That is why return -1 for we are waiting something
     * to happen is 'correct'
     */
    int retCode = -1;

    if ( ptr == NULL )
    {
        return retCode;
    }

    pa_context_state_t state = pa_context_get_state( ptr->context );

    /* Check if context is good. If it is then check state
     * otherwise report error
     *
     * Only with PA_CONTEXT_READY which means
     * application is connected and ready report
     * success
     */
    if( PA_CONTEXT_IS_GOOD(state) )
    {
        switch( state )
        {
            /* These can be found from
             * https://freedesktop.org/software/pulseaudio/doxygen/def_8h.html
             */

            case PA_CONTEXT_READY:
                retCode = PA_OK;
            break;

            case PA_CONTEXT_CONNECTING:
            case PA_CONTEXT_AUTHORIZING:
            case PA_CONTEXT_SETTING_NAME:
                /* These are just here if they are needed in future */
            break;
        }
    }
    else
    {
        retCode = PA_ERR_ACCESS;

        switch( state )
        {
            /* These can be found from
             * https://freedesktop.org/software/pulseaudio/doxygen/def_8h.html
             */

            case PA_CONTEXT_UNCONNECTED:
                PA_DEBUG( ("Portaudio %s: Can't connect to server. (PA_CONTEXT_UNCONNECTED)",
                           __FUNCTION__) );
            break;

            case PA_CONTEXT_TERMINATED:
                PA_DEBUG( ("Portaudio %s: Connection terminated to Pulseaudio server. (PA_CONTEXT_TERMINATED)",
                           __FUNCTION__) );
            break;

            case PA_CONTEXT_FAILED:
                PA_DEBUG( ("Portaudio %s: Connection failed or was disconnected from Pulseaudio server. (PA_CONTEXT_FAILED)",
                           __FUNCTION__) );
            break;
        }
    }

    return retCode;
}

/* Create HostAPI presensentation */
PaPulseAudio_HostApiRepresentation *PaPulseAudio_New( void )
{
    PaPulseAudio_HostApiRepresentation *ptr;
    int fd[2] = { -1, -1 };
    char pulseaudioDeviceName[PAPULSEAUDIO_MAX_DEVICENAME];

    ptr = (PaPulseAudio_HostApiRepresentation *)
    PaUtil_AllocateZeroInitializedMemory(sizeof(PaPulseAudio_HostApiRepresentation));

    /* ptr is NULL if runs out of memory or pointer to allocated memory */
    if( !ptr )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_HostApiRepresentation: Can't allocate memory required for using PulseAudio" );
        return NULL;
    }

    /* Make sure we have NULL all struct first */
    memset(ptr, 0x00, sizeof(PaPulseAudio_HostApiRepresentation));

    ptr->mainloop = pa_threaded_mainloop_new();

    if( !ptr->mainloop )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_HostApiRepresentation: Can't allocate PulseAudio mainloop" );
        goto fail;
    }

    ptr->mainloopApi = pa_threaded_mainloop_get_api(ptr->mainloop);

    /* Use program name as PulseAudio device name */
    snprintf( pulseaudioDeviceName, PAPULSEAUDIO_MAX_DEVICENAME, "%s", __progname );

    ptr->context =
        pa_context_new( pa_threaded_mainloop_get_api(ptr->mainloop), pulseaudioDeviceName );

    if( !ptr->context )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_HostApiRepresentation: Can't instantiate PulseAudio context" );
        goto fail;
    }

    pa_context_set_state_callback( ptr->context, PaPulseAudio_CheckContextStateCb,
                                   ptr );


    if( pa_threaded_mainloop_start( ptr->mainloop ) < 0 )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_HostApiRepresentation: PulseAudio can't start mainloop" );
        goto fail;
    }

    ptr->deviceCount = 0;

    return ptr;

    fail:
    PaPulseAudio_Free( ptr );
    return NULL;
}

/* Free HostAPI */
void PaPulseAudio_Free( PaPulseAudio_HostApiRepresentation * ptr )
{

    /* Sanity check if ptr if NULL don't go anywhere or
     * it will SIGSEGV
     */
    if( !ptr )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_Free: Host API is NULL! Can't do anything about it" );
        return;
    }

    if( ptr->mainloop )
    {
        pa_threaded_mainloop_stop( ptr->mainloop );
    }

    if( ptr->context )
    {
        pa_context_disconnect( ptr->context );
        pa_context_unref( ptr->context );
        ptr->context = NULL;
    }

    if( ptr->mainloopApi && ptr->timeEvent )
    {
        ptr->mainloopApi->time_free( ptr->timeEvent );
        ptr->mainloopApi = NULL;
        ptr->timeEvent = NULL;
    }

    if( ptr->mainloop )
    {
        pa_threaded_mainloop_free( ptr->mainloop );
        ptr->mainloop = NULL;
    }

    if( ptr->allocations )
    {
        PaUtil_FreeAllAllocations( ptr->allocations );
        PaUtil_DestroyAllocationGroup( ptr->allocations );
        ptr->allocations = NULL;
    }

    PaUtil_FreeMemory( ptr );
}

/* If there is drop connection to server this one is called
 * in future it should stop the stream also
 */
void PaPulseAudio_CheckContextStateCb( pa_context * c,
                                      void *userdata )
{
    PaPulseAudio_HostApiRepresentation *ptr =
      (PaPulseAudio_HostApiRepresentation *) userdata;
    /* If this is null we have big problems and we probably are out of memory */
    if( !c )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_CheckContextStateCb: Invalid context " );
        pa_threaded_mainloop_signal( ptr->mainloop, 0 );
        return;
    }

    pa_threaded_mainloop_signal( ptr->mainloop, 0 );
}

/* Server info callback */
void PaPulseAudio_ServerInfoCb( pa_context *c,
                                const pa_server_info *i,
                                void *userdata )
{
    PaPulseAudio_HostApiRepresentation *pulseaudioHostApi =
      (PaPulseAudio_HostApiRepresentation *) userdata;

    if( !c  || !i )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                         "PaPulseAudio_ServerInfoCb: Invalid context or can't get server info" );
        pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, 0 );
        return;
    }

    pulseaudioHostApi->pulseaudioDefaultSampleSpec = i->sample_spec;

    pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, 0 );
}

/* Function adds device to list. It can be input or output stream
 *  or in pulseaudio source or sink.
 */
int _PaPulseAudio_AddAudioDevice( PaPulseAudio_HostApiRepresentation *hostapi,
                                  const char *PaPulseAudio_SinkSourceName,
                                  const char *PaPulseAudio_SinkSourceNameDesc,
                                  int inputChannels,
                                  int outputChannels,
                                  double defaultLowInputLatency,
                                  double defaultHighInputLatency,
                                  double defaultLowOutputLatency,
                                  double defaultHighOutputLatency,
                                  const long defaultSampleRate )
{
    /* These should be at least 1
     *
     * Maximun size of string is 1024 (PAPULSEAUDIO_MAX_DEVICENAME)
     * which should be mostly suffient even pulseaudio device
     * names can be very long
     */
    int pulseaudioRealNameSize = strnlen( PaPulseAudio_SinkSourceNameDesc, (PAPULSEAUDIO_MAX_DEVICENAME - 1) ) + 1;
    int pulseaudioDeviceNameSize = strnlen( PaPulseAudio_SinkSourceName, (PAPULSEAUDIO_MAX_DEVICENAME - 1) ) + 1;
    char *pulseaudioLocalDeviceName = NULL;

    hostapi->deviceInfoArray[hostapi->deviceCount].structVersion = 2;
    hostapi->deviceInfoArray[hostapi->deviceCount].hostApi = hostapi->hostApiIndex;
    hostapi->pulseaudioDeviceNames[hostapi->deviceCount] =
        PaUtil_GroupAllocateZeroInitializedMemory( hostapi->allocations,
                                                   pulseaudioRealNameSize );
    pulseaudioLocalDeviceName = PaUtil_GroupAllocateZeroInitializedMemory( hostapi->allocations,
                                                                pulseaudioDeviceNameSize );
    if( !hostapi->pulseaudioDeviceNames[hostapi->deviceCount] &&
        !pulseaudioLocalDeviceName )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                          "_PaPulseAudio_AddAudioDevice: Can't alloc memory" );
        return paInsufficientMemory;
    }

    /* We can maximum have 1024 (PAPULSEAUDIO_MAX_DEVICECOUNT)
     * devices where to choose which should be mostly enough
     */
    if( hostapi->deviceCount >= PAPULSEAUDIO_MAX_DEVICECOUNT )
    {
        return paDeviceUnavailable;
    }

    snprintf( hostapi->pulseaudioDeviceNames[hostapi->deviceCount],
              pulseaudioRealNameSize,
              "%s",
              PaPulseAudio_SinkSourceNameDesc );
    snprintf( pulseaudioLocalDeviceName,
              pulseaudioDeviceNameSize,
              "%s",
              PaPulseAudio_SinkSourceName );


    hostapi->deviceInfoArray[hostapi->deviceCount].name = pulseaudioLocalDeviceName;

    hostapi->deviceInfoArray[hostapi->deviceCount].maxInputChannels = inputChannels;
    hostapi->deviceInfoArray[hostapi->deviceCount].maxOutputChannels = outputChannels;
    hostapi->deviceInfoArray[hostapi->deviceCount].defaultLowInputLatency = defaultLowInputLatency;
    hostapi->deviceInfoArray[hostapi->deviceCount].defaultLowOutputLatency = defaultLowOutputLatency;
    hostapi->deviceInfoArray[hostapi->deviceCount].defaultHighInputLatency = defaultHighInputLatency;
    hostapi->deviceInfoArray[hostapi->deviceCount].defaultHighOutputLatency = defaultHighOutputLatency;
    hostapi->deviceInfoArray[hostapi->deviceCount].defaultSampleRate = defaultSampleRate;
    hostapi->deviceCount++;

    return paNoError;
}

/* Called when iterating through sinks */
void PaPulseAudio_SinkListCb( pa_context * c,
                              const pa_sink_info * l,
                              int eol,
                              void *userdata )
{
    PaPulseAudio_HostApiRepresentation *pulseaudioHostApi =
        (PaPulseAudio_HostApiRepresentation *) userdata;
    const char *pulseaudioDeviceName = NULL;


    /* If this is null we have big problems and we probably are out of memory */
    if( !c || !l )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_SinkListCb: Invalid context or sink info" );
        goto error;
    }

    /* If eol is set to a positive number, you're at the end of the list */
    if( eol > 0 )
    {
        goto error;
    }

    pulseaudioDeviceName = l->name;

    if( l->description != NULL )
    {
        pulseaudioDeviceName = l->description;
    }

    if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi,
                                      pulseaudioDeviceName,
                                      l->name,
                                      0,
                                      l->sample_spec.channels,
                                      0,
                                      0,
                                      PA_PULSEAUDIO_DEFAULT_MIN_LATENCY,
                                      PA_PULSEAUDIO_DEFAULT_MAX_LATENCY,
                                      l->sample_spec.rate ) != paNoError )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" );
    }

    error:
    pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop,
                                 0 );
}

/* Called when iterating through sources */
void PaPulseAudio_SourceListCb( pa_context * c,
                                const pa_source_info * l,
                                int eol,
                                void *userdata )
{
    PaPulseAudio_HostApiRepresentation *pulseaudioHostApi =
        (PaPulseAudio_HostApiRepresentation *) userdata;
    const char *pulseaudioDeviceName = NULL;


    /* If this is null we have big problems and we probably are out of memory */
    if( !c )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_SourceListCb: Invalid context" );
        goto error;
    }

    /* If eol is set to a positive number, you're at the end of the list */
    if( eol > 0 )
    {
        goto error;
    }

    pulseaudioDeviceName = l->name;

    if( l->description != NULL )
    {
        pulseaudioDeviceName = l->description;
    }

    if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi,
                                      pulseaudioDeviceName,
                                      l->name,
                                      l->sample_spec.channels,
                                      0,
                                      PA_PULSEAUDIO_DEFAULT_MIN_LATENCY,
                                      PA_PULSEAUDIO_DEFAULT_MAX_LATENCY,
                                      0,
                                      0,
                                      l->sample_spec.rate ) != paNoError )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_SourceListCb: Can't add device. Maximum amount reached!" );
    }

    error:
    pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop,
                                 0 );
}

/* This routine is called whenever the stream state changes */
void PaPulseAudio_StreamStateCb( pa_stream * s,
                                 void *userdata )
{
    PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) userdata;
    const pa_buffer_attr *pulseaudioBufferAttr = NULL;
    pa_stream_state_t state = PA_STREAM_UNCONNECTED;

    /* If you need debug pring enable these
     * char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
     */


    /* If this is null we have big problems and we probably are out of memory */
    if( !s )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_StreamStateCb: Invalid stream" );
        return;
    }

    state = pa_stream_get_state(s);

    switch( state )
    {
        case PA_STREAM_CREATING:
            break;

        case PA_STREAM_READY:
            if (!(pulseaudioBufferAttr = pa_stream_get_buffer_attr(s)))
            {
                PA_DEBUG( ("Portaudio %s: Can get buffer attr: '%s'\n",
                           __FUNCTION__,
                           pa_strerror(pa_context_errno(pa_stream_get_context(s) ) )) );
            }
            else
            {
                PA_DEBUG( ("%s: %s Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u, fragsize=%u\n",
                           __FUNCTION__, pa_stream_get_device_name(s),
                           pulseaudioBufferAttr->maxlength, pulseaudioBufferAttr->tlength, pulseaudioBufferAttr->prebuf,
                           pulseaudioBufferAttr->minreq, pulseaudioBufferAttr->maxlength, pulseaudioBufferAttr->fragsize) );
            }
            break;

        case PA_STREAM_TERMINATED:
            PA_DEBUG( ("Portaudio %s: PA_STREAM_TERMINATED '%s'\n",
                      __FUNCTION__,
                      pa_strerror( pa_context_errno( pa_stream_get_context( s ) ) )) );
            break;

        case PA_STREAM_FAILED:
        default:
            PA_DEBUG( ("Portaudio %s: PA_STREAM_FAILED '%s'\n",
                      __FUNCTION__,
                      pa_strerror( pa_context_errno( pa_stream_get_context( s ) ) )) );

            break;
    }
}

/* If stream is underflowed then this callback is called
 * one needs to enable debug to make use os this
 *
 * Otherwise it's used to update error message
 */
void PaPulseAudio_StreamUnderflowCb( pa_stream *s,
                                     void *userdata )
{
    PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) userdata;
    pa_buffer_attr *pulseaudioOutputSampleSpec = NULL;

    /* If this is null we have big problems and we probably are out of memory */
    if( !s )
    {
        PA_DEBUG( ("Portaudio %s: Invalid stream",
                   __FUNCTION__) );
        return;
    }

    stream->outputUnderflows++;
    pulseaudioOutputSampleSpec = (pa_buffer_attr *)pa_stream_get_buffer_attr(s);
    PA_DEBUG( ("Portaudio %s: PulseAudio '%s' with delay: %ld stream has underflowed\n",
               __FUNCTION__,
               pa_stream_get_device_name(s),
               pulseaudioOutputSampleSpec->tlength) );

    pa_threaded_mainloop_signal( stream->mainloop,
                                 0 );
}

/* Initialize HostAPI */
PaError PaPulseAudio_Initialize( PaUtilHostApiRepresentation ** hostApi,
                                 PaHostApiIndex hostApiIndex )
{
    PaError result = paNoError;
    int i;
    int ret = 0;
    int lockTaken = 0;
    PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = NULL;
    PaDeviceInfo *deviceInfoArray = NULL;

    pa_operation *pulseaudioOperation = NULL;

    pulseaudioHostApi = PaPulseAudio_New();

    if( !pulseaudioHostApi )
    {
        result = paInsufficientMemory;
        goto error;
    }

    pulseaudioHostApi->allocations = PaUtil_CreateAllocationGroup();

    if( !pulseaudioHostApi->allocations )
    {
        result = paInsufficientMemory;
        goto error;
    }

    pulseaudioHostApi->hostApiIndex = hostApiIndex;
    *hostApi = &pulseaudioHostApi->inheritedHostApiRep;
    (*hostApi)->info.structVersion = 1;
    (*hostApi)->info.type = paPulseAudio;
    (*hostApi)->info.name = "PulseAudio";

    (*hostApi)->info.defaultInputDevice = paNoDevice;
    (*hostApi)->info.defaultOutputDevice = paNoDevice;

    /* Connect to server */
    PaPulseAudio_Lock( pulseaudioHostApi->mainloop );
    lockTaken = 1;

    ret = pa_context_connect( pulseaudioHostApi->context,
                                 NULL,
                                 0,
                                 NULL );

    if( ret < 0 )
    {
        PA_DEBUG( ("Portaudio %s: Can't connect to server",
                   __FUNCTION__) );
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( ret,
                                           "PulseAudio_Initialize: Can't connect to server");
        result = paUnanticipatedHostError;
        goto error;
    }

    ret = 0;

    /* We should wait that PulseAudio server let us in or fails us */
    while( !ret )
    {
        pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop );

        result = PaPulseAudio_CheckConnection( pulseaudioHostApi );

        if( result > PA_OK )
        {
            goto error;
        }

        if( result == PA_OK )
        {
            ret = 1;
        }
    }

    memset( pulseaudioHostApi->deviceInfoArray,
            0x00,
            sizeof(PaDeviceInfo) * PAPULSEAUDIO_MAX_DEVICECOUNT );
    for (i = 0; i < PAPULSEAUDIO_MAX_DEVICECOUNT; i++)
    {
        pulseaudioHostApi->pulseaudioDeviceNames[i] = NULL;
    }

    /* Get info about server. This returns Default sink and soure name. */
    pulseaudioOperation =
    pa_context_get_server_info( pulseaudioHostApi->context,
                                PaPulseAudio_ServerInfoCb,
                                pulseaudioHostApi );

    PaPulseAudio_UnLock( pulseaudioHostApi->mainloop );
    lockTaken = 0;

    PaPulseAudio_ReleaseOperation( pulseaudioHostApi,
                                   &pulseaudioOperation );

    PaPulseAudio_Lock( pulseaudioHostApi->mainloop );
    lockTaken = 1;

    /* Add the "Default" sink at index 0 */
    if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi,
                                      "Default Sink",
                                      "The PulseAudio default sink",
                                      0,
                                      PA_CHANNELS_MAX,
                                      0,
                                      0,
                                      PA_PULSEAUDIO_DEFAULT_MIN_LATENCY,
                                      PA_PULSEAUDIO_DEFAULT_MAX_LATENCY,
                                      pulseaudioHostApi->pulseaudioDefaultSampleSpec.rate ) != paNoError )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" );
    } else {
        pulseaudioHostApi->inheritedHostApiRep.info.defaultOutputDevice =
                pulseaudioHostApi->deviceCount - 1;
    }

    /* Add the "Default" source at index 1 */
    if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi,
                                      "Default Source",
                                      "The PulseAudio default source",
                                      PA_CHANNELS_MAX,
                                      0,
                                      PA_PULSEAUDIO_DEFAULT_MIN_LATENCY,
                                      PA_PULSEAUDIO_DEFAULT_MAX_LATENCY,
                                      0,
                                      0,
                                      pulseaudioHostApi->pulseaudioDefaultSampleSpec.rate ) != paNoError )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" );
    } else {
        pulseaudioHostApi->inheritedHostApiRep.info.defaultInputDevice =
                pulseaudioHostApi->deviceCount - 1;
    }

    /* List PulseAudio sinks. If found callback: PaPulseAudio_SinkListCb */
    pulseaudioOperation =
        pa_context_get_sink_info_list( pulseaudioHostApi->context,
                                       PaPulseAudio_SinkListCb,
                                       pulseaudioHostApi );

    PaPulseAudio_UnLock( pulseaudioHostApi->mainloop );
    lockTaken = 0;

    PaPulseAudio_ReleaseOperation( pulseaudioHostApi,
                                   &pulseaudioOperation );

    PaPulseAudio_Lock( pulseaudioHostApi->mainloop );
    lockTaken = 1;

    /* List PulseAudio sources. If found callback: PaPulseAudio_SourceListCb */
    pulseaudioOperation =
        pa_context_get_source_info_list( pulseaudioHostApi->context,
                                         PaPulseAudio_SourceListCb,
                                         pulseaudioHostApi );

    PaPulseAudio_UnLock( pulseaudioHostApi->mainloop );
    lockTaken = 0;

    PaPulseAudio_ReleaseOperation( pulseaudioHostApi,
                                   &pulseaudioOperation );

    PaPulseAudio_Lock( pulseaudioHostApi->mainloop );
    lockTaken = 1;

    (*hostApi)->info.deviceCount = pulseaudioHostApi->deviceCount;

    if( pulseaudioHostApi->deviceCount > 0 )
    {
        /* If you have over 1024 Audio devices.. shame on you! */

        (*hostApi)->deviceInfos =
            (PaDeviceInfo **)
            PaUtil_GroupAllocateZeroInitializedMemory( pulseaudioHostApi->allocations,
                                                       sizeof(PaDeviceInfo *) *
                                                       pulseaudioHostApi->deviceCount );

        if( !(*hostApi)->deviceInfos )
        {
            result = paInsufficientMemory;
            goto error;
        }

        for ( i = 0; i < pulseaudioHostApi->deviceCount; i++ )
        {
            (*hostApi)->deviceInfos[i] =
                &pulseaudioHostApi->deviceInfoArray[i];
        }
    }

    (*hostApi)->Terminate = Terminate;
    (*hostApi)->OpenStream = OpenStream;
    (*hostApi)->IsFormatSupported = IsFormatSupported;

    PaUtil_InitializeStreamInterface( &pulseaudioHostApi->callbackStreamInterface,
                                      PaPulseAudio_CloseStreamCb,
                                      PaPulseAudio_StartStreamCb,
                                      PaPulseAudio_StopStreamCb,
                                      PaPulseAudio_AbortStreamCb,
                                      IsStreamStopped,
                                      IsStreamActive,
                                      GetStreamTime,
                                      GetStreamCpuLoad,
                                      PaUtil_DummyRead,
                                      PaUtil_DummyWrite,
                                      PaUtil_DummyGetReadAvailable,
                                      PaUtil_DummyGetWriteAvailable );

    PaUtil_InitializeStreamInterface( &pulseaudioHostApi->blockingStreamInterface,
                                      PaPulseAudio_CloseStreamCb,
                                      PaPulseAudio_StartStreamCb,
                                      PaPulseAudio_StopStreamCb,
                                      PaPulseAudio_AbortStreamCb,
                                      IsStreamStopped,
                                      IsStreamActive,
                                      GetStreamTime,
                                      PaUtil_DummyGetCpuLoad,
                                      PaPulseAudio_ReadStreamBlock,
                                      PaPulseAudio_WriteStreamBlock,
                                      PaPulseAudio_GetStreamReadAvailableBlock,
                                      PaUtil_DummyGetWriteAvailable );

    PaPulseAudio_UnLock( pulseaudioHostApi->mainloop );
    lockTaken = 0;
    return result;

    error:

    if( pulseaudioHostApi )
    {
        if( lockTaken )
        {
            PaPulseAudio_UnLock( pulseaudioHostApi->mainloop );
            lockTaken = 0;
        }
        PaPulseAudio_Free( pulseaudioHostApi );
        pulseaudioHostApi = NULL;
    }

    return result;
}

/* Drop stream now */
void Terminate( struct PaUtilHostApiRepresentation *hostApi )
{
    PaPulseAudio_HostApiRepresentation *pulseaudioHostApi =
        (PaPulseAudio_HostApiRepresentation *) hostApi;

    PaPulseAudio_Lock( pulseaudioHostApi->mainloop );
    pa_context_disconnect( pulseaudioHostApi->context );
    PaPulseAudio_UnLock( pulseaudioHostApi->mainloop );

    PaPulseAudio_Free( pulseaudioHostApi );
}

/* Checks from pulseaudio that is format supported */
PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
                           const PaStreamParameters * inputParameters,
                           const PaStreamParameters * outputParameters,
                           double sampleRate )
{
    int inputChannelCount,
     outputChannelCount;
    PaSampleFormat inputSampleFormat,
     outputSampleFormat;

    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;
        }

        /* check that input device can support inputChannelCount */
        if( inputChannelCount >
            hostApi->deviceInfos[inputParameters->device]->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;
        }

        /* check that output device can support outputChannelCount */
        if( outputChannelCount >
            hostApi->deviceInfos[outputParameters->device]->maxOutputChannels )
        {
            return paInvalidChannelCount;
        }

        /* validate outputStreamInfo */
        if( outputParameters->hostApiSpecificStreamInfo )
        {
            /* this implementation doesn't use custom stream info */
            return paIncompatibleHostApiSpecificStreamInfo;
        }

    }

    else
    {
        outputChannelCount = 0;
    }

    return paFormatIsSupported;
}

/* Makes conversion from portaudio to pulseaudio sample defines
 * Little endian formats are used (if there is some mystical big endian
 * sound device this should be fixed but until then it's safe to believe
 * this works
 */
PaError PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_( PaSampleFormat portaudiosf,
                                                            pa_sample_spec * pulseaudiosf )
{
    switch( portaudiosf )
    {
        case paFloat32:
            pulseaudiosf->format = PA_SAMPLE_FLOAT32LE;
            break;

        case paInt32:
            pulseaudiosf->format = PA_SAMPLE_S32LE;
            break;

        case paInt24:
            pulseaudiosf->format = PA_SAMPLE_S24LE;
            break;

        case paInt16:
            pulseaudiosf->format = PA_SAMPLE_S16LE;
            break;

        case paInt8:
            pulseaudiosf->format = PA_SAMPLE_U8;
            break;

        case paUInt8:
            pulseaudiosf->format = PA_SAMPLE_U8;
            break;

        case paCustomFormat:
        case paNonInterleaved:
            PA_DEBUG(("PaPulseAudio %s: THIS IS NOT SUPPORTED BY PULSEAUDIO!\n",
                      __FUNCTION__));
            return paSampleFormatNotSupported;
            break;
    }

    return paNoError;
}


/* Allocate buffer. */
PaError PaPulseAudio_BlockingInitRingBuffer( PaUtilRingBuffer * rbuf,
                                             int size )
{
    char *ringbufferBuffer = (char *) malloc( size );
    PaError ret = paNoError;

    if( ringbufferBuffer == NULL )
    {
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_BlockingInitRingBuffer: Not enough memory to handle request" );
        return paInsufficientMemory;
    }

    memset( ringbufferBuffer,
            0x00,
            size );

    ret = PaUtil_InitializeRingBuffer( rbuf,
                                       1,
                                       size,
                                       ringbufferBuffer );

    if( ret < paNoError )
    {
        free( ringbufferBuffer );
        PA_DEBUG( ("Portaudio %s: Can't initialize input ringbuffer with size: %ld!\n",
                   __FUNCTION__, size) );
        PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0,
                                           "PaPulseAudio_BlockingInitRingBuffer: Can't initialize input ringbuffer" );

        return paNotInitialized;
    }

    return paNoError;
}

/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */

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;
    PaPulseAudio_HostApiRepresentation *pulseaudioHostApi =
        (PaPulseAudio_HostApiRepresentation *) hostApi;
    PaPulseAudio_Stream *stream = NULL;
    unsigned long framesPerHostBuffer = framesPerBuffer;        /* these may not be equivalent for all implementations */
    int inputChannelCount,
     outputChannelCount;
    PaSampleFormat inputSampleFormat,
     outputSampleFormat;
    PaSampleFormat hostInputSampleFormat,
     hostOutputSampleFormat;

    /* validate platform specific flags */
    if( (streamFlags & paPlatformSpecificFlags) != 0 )
    {
        return paInvalidFlag;
    }

    /* This is something that Pulseaudio can handle
     * and it's also bearable small
     */
    if( framesPerBuffer == paFramesPerBufferUnspecified )
    {
        framesPerBuffer = PAPULSEAUDIO_FRAMESPERBUFFERUNSPEC;
    }

    PaPulseAudio_Lock(pulseaudioHostApi->mainloop);
    stream =
        (PaPulseAudio_Stream *) PaUtil_AllocateZeroInitializedMemory( sizeof( PaPulseAudio_Stream ) );

    if( !stream )
    {
        result = paInsufficientMemory;
        goto openstream_error;
    }

    /* Allocate memory for source and sink names. */
    const char defaultSourceStreamName[] = "Portaudio source";
    const char defaultSinkStreamName[] = "Portaudio sink";

    stream->framesPerHostCallback = framesPerBuffer;
    stream->inputStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( sizeof( defaultSourceStreamName ) );
    stream->outputStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( sizeof( defaultSinkStreamName ) );
    if ( !stream->inputStreamName || !stream->outputStreamName )
    {
        result = paInsufficientMemory;
        goto openstream_error;
    }

    /* Copy initial stream names to memory. */
    memcpy( stream->inputStreamName, defaultSourceStreamName, sizeof( defaultSourceStreamName ) );
    memcpy( stream->outputStreamName, defaultSinkStreamName, sizeof( defaultSinkStreamName ) );

    stream->isActive = 0;
    stream->isStopped = 1;
    stream->pulseaudioIsActive = 0;
    stream->pulseaudioIsStopped = 1;

    stream->inputStream = NULL;
    stream->outputStream = NULL;
    memset( &stream->inputRing,
            0x00,
            sizeof( PaUtilRingBuffer ) );

    if( inputParameters )
    {
        inputChannelCount = inputParameters->channelCount;
        inputSampleFormat = inputParameters->sampleFormat;

        /* unless alternate device specification is supported, reject the use of
         * paUseHostApiSpecificDeviceSpecification
         */
        if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
        {
            result = paInvalidDevice;
            goto openstream_error;
        }

        /* check that input device can support inputChannelCount */
        if( inputChannelCount >
            hostApi->deviceInfos[inputParameters->device]->maxInputChannels )
        {
            result = paInvalidChannelCount;
            goto openstream_error;
        }

        /* validate inputStreamInfo */
        if( inputParameters->hostApiSpecificStreamInfo )
        {
            /* this implementation doesn't use custom stream info */
            result = paIncompatibleHostApiSpecificStreamInfo;
            goto openstream_error;
        }

        hostInputSampleFormat =
            PaUtil_SelectClosestAvailableFormat( inputSampleFormat,
                                                 inputSampleFormat );

        stream->inputFrameSize = Pa_GetSampleSize( inputSampleFormat ) * inputChannelCount;

        result = PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_(
            hostInputSampleFormat,
            &stream->inputSampleSpec );

        if( result != paNoError )
        {
            goto openstream_error;
        }

        stream->inputSampleSpec.rate = sampleRate;
        stream->inputSampleSpec.channels = inputChannelCount;
        stream->inputChannelCount = inputChannelCount;

        if( !pa_sample_spec_valid( &stream->inputSampleSpec ) )
        {
            PA_DEBUG( ("Portaudio %s: Invalid input audio spec!\n",
                      __FUNCTION__) );
            result = paUnanticipatedHostError; /* should have been caught above */
            goto openstream_error;
        }

        stream->inputStream =
            pa_stream_new( pulseaudioHostApi->context,
                           stream->inputStreamName,
                           &stream->inputSampleSpec,
                           NULL );

        if( stream->inputStream != NULL )
        {
            pa_stream_set_state_callback( stream->inputStream,
                                          PaPulseAudio_StreamStateCb,
                                          stream);
            pa_stream_set_started_callback( stream->inputStream,
                                            PaPulseAudio_StreamStartedCb,
                                            stream );
        }
        else
        {
            PA_DEBUG( ("Portaudio %s: Can't alloc input stream!\n",
                       __FUNCTION__) );
        }

        stream->inputDevice = inputParameters->device;

        /* Convert positive suggestedLatency from seconds to microseconds, otherwise default to zero. */
        if (inputParameters->suggestedLatency >= 0)
        {
            stream->suggestedLatencyUSecs = (unsigned int) (inputParameters->suggestedLatency * 1e6 + 1.0f);
        }
        else
        {
            stream->suggestedLatencyUSecs = 0;
        }

        /*
         * This is too much as most of the time there is not much
         * stuff in buffer but it's enough if we are doing blocked
         * and reading is somewhat slower than callback
         */
        result = PaPulseAudio_BlockingInitRingBuffer( &stream->inputRing,
                                                      (65536 * 4) );
        if( result != paNoError )
        {
            goto openstream_error;
        }

    }

    else
    {
        inputChannelCount = 0;
        inputSampleFormat = hostInputSampleFormat = paFloat32;
    }

    if( outputParameters )
    {
        outputChannelCount = outputParameters->channelCount;
        outputSampleFormat = outputParameters->sampleFormat;

        /* unless alternate device specification is supported, reject the use of
         * paUseHostApiSpecificDeviceSpecification
         */
        if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
        {
            result = paInvalidDevice;
            goto openstream_error;
        }

        /* check that output device can support inputChannelCount */
        if( outputChannelCount >
            hostApi->deviceInfos[outputParameters->device]->maxOutputChannels )
        {
            result = paInvalidChannelCount;
            goto openstream_error;
        }

        /* validate outputStreamInfo */
        if( outputParameters->hostApiSpecificStreamInfo )
        {
            result = paIncompatibleHostApiSpecificStreamInfo;   /* this implementation doesn't use custom stream info */
            goto openstream_error;
        }

        /* IMPLEMENT ME - establish which  host formats are available */
        hostOutputSampleFormat =
            PaUtil_SelectClosestAvailableFormat( outputSampleFormat
                                                 /* native formats */ ,
                                                 outputSampleFormat );

        stream->outputFrameSize =
            Pa_GetSampleSize( outputSampleFormat ) * outputChannelCount;

        result = PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_(
            hostOutputSampleFormat,
            &stream->outputSampleSpec );

        if( result != paNoError )
        {
            goto openstream_error;
        }

        stream->outputSampleSpec.rate = sampleRate;
        stream->outputSampleSpec.channels = outputChannelCount;
        stream->outputChannelCount = outputChannelCount;

        if( !pa_sample_spec_valid( &stream->outputSampleSpec ) )
        {
            PA_DEBUG( ("Portaudio %s: Invalid audio spec for output!\n",
                      __FUNCTION__) );
            result = paUnanticipatedHostError; /* should have been caught above */
            goto openstream_error;
        }

        stream->outputStream =
            pa_stream_new( pulseaudioHostApi->context,
                           stream->outputStreamName,
                           &stream->outputSampleSpec,
                           NULL );

        if( stream->outputStream != NULL )
        {
            pa_stream_set_state_callback( stream->outputStream,
                                          PaPulseAudio_StreamStateCb,
                                          stream );
            pa_stream_set_started_callback( stream->outputStream,
                                            PaPulseAudio_StreamStartedCb,
                                            stream );
            pa_stream_set_underflow_callback( stream->outputStream,
                                              PaPulseAudio_StreamUnderflowCb,
                                              stream);

        }

        else
        {
            PA_DEBUG( ("Portaudio %s: Can't alloc output stream!\n",
                      __FUNCTION__) );
        }

        if( result != paNoError )
        {
            goto openstream_error;
        }

        stream->outputDevice = outputParameters->device;

        /* Convert positive suggestedLatency from seconds to microseconds, otherwise default to zero. */
        if (outputParameters->suggestedLatency >= 0)
        {
            stream->suggestedLatencyUSecs = (unsigned int) (outputParameters->suggestedLatency * 1e6 + 1.0f);
        }
        else
        {
            stream->suggestedLatencyUSecs = 0;
        }
    }

    else
    {
        outputChannelCount = 0;
        outputSampleFormat = hostOutputSampleFormat = paFloat32;
    }

    stream->hostapi = pulseaudioHostApi;
    stream->context = pulseaudioHostApi->context;
    stream->mainloop = pulseaudioHostApi->mainloop;

    if( streamCallback )
    {
        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
                                               &pulseaudioHostApi->callbackStreamInterface,
                                               streamCallback,
                                               userData );
    }
    else
    {
        PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
                                               &pulseaudioHostApi->blockingStreamInterface,
                                               streamCallback,
                                               userData );
    }

    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,
                                               paUtilUnknownHostBufferSize,
                                               streamCallback,
                                               userData );

    if( result != paNoError )
    {
        goto openstream_error;
    }

    /* inputLatency is specified in _seconds_ */
    stream->streamRepresentation.streamInfo.inputLatency =
        (PaTime) PaUtil_GetBufferProcessorInputLatencyFrames(
            &stream->bufferProcessor ) / sampleRate;

    /* outputLatency is specified in _seconds_ */
    stream->streamRepresentation.streamInfo.outputLatency =
        (PaTime) PaUtil_GetBufferProcessorOutputLatencyFrames(
            &stream->bufferProcessor ) / sampleRate;

    stream->streamRepresentation.streamInfo.sampleRate = sampleRate;

    stream->maxFramesHostPerBuffer = framesPerBuffer;
    stream->maxFramesPerBuffer = framesPerBuffer;

    *s = (PaStream *) stream;

    openstream_end:
    PaPulseAudio_UnLock( pulseaudioHostApi->mainloop );
    return result;

    openstream_error:

    if( stream )
    {
        /* If the blocking input ring buffer was allocated, release it. */
        if( stream->inputRing.buffer )
        {
            free( stream->inputRing.buffer );
            stream->inputRing.buffer = NULL;
        }

        PaUtil_FreeMemory( stream->inputStreamName );
        PaUtil_FreeMemory( stream->outputStreamName );
        PaUtil_FreeMemory( stream );
    }

    goto openstream_end;
}

PaError IsStreamStopped( PaStream * s )
{
    PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s;
    return stream->isStopped;
}


PaError IsStreamActive( PaStream * s )
{
    PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s;
    return stream->isActive;
}


PaTime GetStreamTime( PaStream * s )
{
    PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s;
    PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi;
    PaStreamCallbackTimeInfo timeInfo = { 0, 0, 0 };

    PaPulseAudio_Lock( pulseaudioHostApi->mainloop );

    if( stream->outputStream )
    {
        if( PaPulseAudio_updateTimeInfo( stream->outputStream,
                                         &timeInfo,
                                         0 ) == -PA_ERR_NODATA )
        {
            return 0;
        }
    }

    if( stream->inputStream )
    {
        if( PaPulseAudio_updateTimeInfo( stream->inputStream,
                                         &timeInfo,
                                         1 )  == -PA_ERR_NODATA )
        {
            return 0;
        }
    }

    PaPulseAudio_UnLock( pulseaudioHostApi->mainloop );
    return timeInfo.currentTime;
}


double GetStreamCpuLoad( PaStream * s )
{
    PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s;

    return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
}

/** Extensions */
static void RenameStreamCb(pa_stream *s, int success, void *userdata)
{
    /* Currently does nothing but signal the caller. */
    PaPulseAudio_Stream *l_ptrStream = (PaPulseAudio_Stream *) userdata;
    pa_threaded_mainloop_signal( l_ptrStream->mainloop,
                                 0 );
}

PaError PaPulseAudio_RenameSource( PaStream *s, const char *streamName )
{
    PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s;
    PaError result = paNoError;
    pa_operation *op = NULL;

    if ( stream->inputStream == NULL )
    {
        return paInvalidDevice;
    }

    /* Reallocate stream name in memory. */
    PaPulseAudio_Lock( stream->mainloop );
    char *newStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1 );
    if ( !newStreamName )
    {
        PaPulseAudio_UnLock( stream->mainloop );
        return paInsufficientMemory;
    }
    snprintf( newStreamName, strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1, "%s", streamName );

    PaUtil_FreeMemory( stream->inputStreamName );
    stream->inputStreamName = newStreamName;

    op = pa_stream_set_name( stream->inputStream, streamName, RenameStreamCb, stream );
    PaPulseAudio_UnLock( stream->mainloop );

    PaPulseAudio_ReleaseOperation( stream->hostapi,
                                   &op );

    return result;
}

PaError PaPulseAudio_RenameSink( PaStream *s, const char *streamName )
{
    PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s;
    PaError result = paNoError;
    pa_operation *op = NULL;

    if ( stream->outputStream == NULL )
    {
        return paInvalidDevice;
    }

    /* Reallocate stream name in memory. */
    PaPulseAudio_Lock( stream->mainloop );
    char *newStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1 );
    if ( !newStreamName )
    {
        PaPulseAudio_UnLock( stream->mainloop );
        return paInsufficientMemory;
    }
    snprintf( newStreamName, strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1, "%s", streamName );

    PaUtil_FreeMemory( stream->outputStreamName );
    stream->outputStreamName = newStreamName;

    op = pa_stream_set_name( stream->outputStream, streamName, RenameStreamCb, stream );
    PaPulseAudio_UnLock( stream->mainloop );

    /* Wait for completion. */
    PaPulseAudio_ReleaseOperation( stream->hostapi,
                                   &op );

    return result;
}
