media/liboggplay_audio/sydney_audio_waveapi.c
author Daniel Holbert <dholbert@cs.stanford.edu>
Wed, 01 Oct 2008 23:49:45 -0700
changeset 20072 d47a01a87c6c07e894685d7994d901d351afaea0
parent 17095 397b37c682eda395661191712183b68f110c6c3c
permissions -rw-r--r--
Bug 458167 - (comments-only changes) Grammar Nazi Vol1: s/it's/its/ for the posessive version of the word, and a few other misc comment-typos, in these directories: accessible browser config profile rdf toolkit tools xpcom xpfe

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 * CSIRO
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): Marcin Lubonski 
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** *
 */

#include "sydney_audio.h"
#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <mmreg.h>
#include <mmsystem.h>
#include <math.h>


// FIX ME: block size and block should be determined based on the OggPlay offset 
// for audio track
#define BLOCK_SIZE  2560
#define BLOCK_COUNT 32
#define DEFAULT_DEVICE_NAME "Default WAVE Device"
#define DEFAULT_DEVICE WAVE_MAPPER

#define VERBOSE_OUTPUT 1

// INFO: if you get weird compile errors make sure there is no extra chars pass '\' 
#if defined(VERBOSE_OUTPUT)
#define WAVE_ERROR_VERBOSE(error, message) \
  switch (error) { \
    case MMSYSERR_ALLOCATED: \
      printf("[WAVE API] Device allocation error returned while executing %s\n", message); \
      break; \
    case MMSYSERR_BADDEVICEID: \
      printf("[WAVE API] Wrong device ID error returned while executing %s\n", message); \
      break; \
    case MMSYSERR_NODRIVER: \
      printf("[WAVE API] System driver not present error returned while executing %s\n", message); \
      break; \
    case MMSYSERR_INVALHANDLE: \
      printf("[WAVE API] Invalid device handle error returned while executing %s\n", message); \
      break; \
    case MMSYSERR_NOMEM: \
      printf("[WAVE API] No memory error returned while executing %s\n", message); \
      break; \
    case MMSYSERR_NOTSUPPORTED: \
      printf("[WAVE API] Not supported error returned while executing %s\n", message); \
      break; \
    case WAVERR_BADFORMAT: \
      printf("[WAVE API] Not valid audio format returned while executing %s\n", message); \
      break; \
    case WAVERR_SYNC: \
      printf("[WAVE API] Device synchronous error returned while executing %s\n", message); \
      break; \
    default: \
      printf("[WAVE API] Error while executing %s\n", message); \
      break; \
  }
#else
#define WAVE_ERROR_VERBOSE(error, message) \
  do {} while(0)
#endif

#define HANDLE_WAVE_ERROR(status, location) \
  if (status != MMSYSERR_NOERROR) { \
      WAVE_ERROR_VERBOSE(status, location); \
      return getSAErrorCode(status); \
  }

#define ERROR_IF_NO_INIT(handle) \
  if (handle == NULL) { \
		return SA_ERROR_NO_INIT; \
	}

/* local implementation of the sa_stream_t type */
struct sa_stream {   
  char*           deviceName;
  UINT				    device;
  UINT				    channels;
  UINT				    rate;
	
  sa_mode_t			  rwMode;
  sa_pcm_format_t	format;   
 
  HWAVEOUT			  hWaveOut;
  HANDLE			    callbackEvent;
  CRITICAL_SECTION  waveCriticalSection;  
  WAVEHDR*			  waveBlocks;  
  volatile int		waveFreeBlockCount;
  int				      waveCurrentBlock;
};


/** Forward definitions of audio api specific functions */
int allocateBlocks(int size, int count, WAVEHDR** blocks);
int freeBlocks(WAVEHDR* blocks);
int openAudio(sa_stream_t *s);
int closeAudio(sa_stream_t * s);
int writeAudio(sa_stream_t *s, LPSTR data, int bytes);
int getSAErrorCode(int waveErrorCode);

void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, 
    DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);

/** Normal way to open a PCM device */
int sa_stream_create_pcm(sa_stream_t **s, 
                         const char *client_name, 
                         sa_mode_t mode, 
                         sa_pcm_format_t format, 
                         unsigned int rate, 
                         unsigned int nchannels) {
  sa_stream_t * _s = NULL;
  
  ERROR_IF_NO_INIT(s);
  
  *s = NULL;
  
  /* FIX ME: for formats different than PCM extend using WAVEFORMATEXTENSIBLE */
  if (format != SA_PCM_FORMAT_S16_NE) {
    return SA_ERROR_NOT_SUPPORTED;
  }

  if (mode != SA_MODE_WRONLY) {
    return SA_ERROR_NOT_SUPPORTED;
  }

  if ((_s = (sa_stream_t*)malloc(sizeof(sa_stream_t))) == NULL) {
    return SA_ERROR_OOM;
  }
   
  _s->rwMode = mode;
  _s->format = format;
  _s->rate = rate;
  _s->channels = nchannels;
  _s->deviceName = DEFAULT_DEVICE_NAME;
  _s->device = DEFAULT_DEVICE;

  *s = _s; 
  return SA_SUCCESS;
}

/** Initialise the device */
int sa_stream_open(sa_stream_t *s) {  
  int status = SA_SUCCESS;

  ERROR_IF_NO_INIT(s);

  switch (s->rwMode) {
    case SA_MODE_WRONLY: 
      status = openAudio(s);
      break;
	default:
      status = SA_ERROR_NOT_SUPPORTED;      
      break;
  }    
  return status;
}

/** Interleaved playback function */
int sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
  int status = SA_SUCCESS;

  ERROR_IF_NO_INIT(s);

  status = writeAudio(s, (LPSTR)data, nbytes);

  return status;
}

/** Close/destroy everything */
int sa_stream_destroy(sa_stream_t *s) {
  int status;

  ERROR_IF_NO_INIT(s);
  /* close and release all allocated resources */
  status = closeAudio(s);

  return status;
}

#define LEFT_CHANNEL_MASK 0x0000FFFF
#define RIGHT_CHANNEL_MASK 0xFFFF0000

/** 
 * retrieved volume as an int in a scale from 0x0000 to 0xFFFF
 * only one value for all channels
 */
int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n) {
  int status;
	DWORD volume;
	WORD left;
	WORD right;

	ERROR_IF_NO_INIT(s);
  
	status = waveOutGetVolume(s->hWaveOut, &volume);
	HANDLE_WAVE_ERROR(status, "reading audio volume level");

	left = volume & LEFT_CHANNEL_MASK;
	right = (volume & RIGHT_CHANNEL_MASK) >> 16;
  vol[0] = (int32_t)(left + right /2);	

	return SA_SUCCESS;

}

/** changes volume as an int in a scale from 0x0000 to 0xFFFF*/
int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n) {
  int status;
	DWORD volume;
	WORD left;
	WORD right;
	
	ERROR_IF_NO_INIT(s);
	
  volume = (DWORD)vol[0];
	left = volume & LEFT_CHANNEL_MASK;	  
	right = left;	  
	volume =  (left << 16) | right;	
	
	status = waveOutSetVolume(s->hWaveOut, volume);
	HANDLE_WAVE_ERROR(status, "setting new audio volume level");	

	return SA_SUCCESS;


}

/** sync/timing */
int sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
	int status;
  MMTIME  mm;

  ERROR_IF_NO_INIT(s);

  if (position != SA_POSITION_WRITE_HARDWARE) {
    return SA_ERROR_NOT_SUPPORTED;
  }
  // request playback progress in bytes
  mm.wType = TIME_BYTES;		
	status = waveOutGetPosition(s->hWaveOut, &mm, sizeof(MMTIME));
  HANDLE_WAVE_ERROR(status, "reading audio buffer position");
  *pos = (int64_t)mm.u.cb;

	return SA_SUCCESS;
}

/* Control/xrun */
/** Resume playing after a pause */
int sa_stream_resume(sa_stream_t *s) {
  int status;  
  
  ERROR_IF_NO_INIT(s);

  status = waveOutRestart(s->hWaveOut);
  HANDLE_WAVE_ERROR(status, "resuming audio playback");

  return SA_SUCCESS;
}
/** Pause audio playback (do not empty the buffer) */
int sa_stream_pause(sa_stream_t *s) {
  int status;

  ERROR_IF_NO_INIT(s);
  
  status = waveOutPause(s->hWaveOut);
  HANDLE_WAVE_ERROR(status, "resuming audio playback");

  return SA_SUCCESS;
}

/*
 * -----------------------------------------------------------------------------
 * Private WAVE API specific functions
 * -----------------------------------------------------------------------------
 */

/** 
 * \brief - allocate buffer for writing to system WAVE audio device
 * \param size - size of each audio block
 * \param cound - number of blocks to be allocated
 * \param blocks - pointer to the blocks buffer to be allocated
 * \return - completion status
 */
int allocateBlocks(int size, int count, WAVEHDR** blocks)
{
  unsigned char* buffer;    
  int i;    
  WAVEHDR* headers;
  DWORD totalBufferSize = (size + sizeof(WAVEHDR)) * count;
    
  /* allocate memory on heap for the entire set in one go  */    
  if((buffer = HeapAlloc(
     GetProcessHeap(), 
     HEAP_ZERO_MEMORY, 
     totalBufferSize
     )) == NULL) {
      printf("Memory allocation error\n");
      return SA_ERROR_OOM;
    }

  /* and set up the pointers to each bit */
  headers = *blocks = (WAVEHDR*)buffer;
  buffer += sizeof(WAVEHDR) * count;
  for(i = 0; i < count; i++) {    
	  headers[i].dwBufferLength = size;
    headers[i].lpData = buffer;
    buffer += size;
  }
    
  return SA_SUCCESS;
}

/**
 * \brief - free allocated audio buffer
 * \param blocks - pointer to allocated the buffer of audio bloks
 * \return - completion status
 */
int freeBlocks(WAVEHDR* blocks)
{    
  if (blocks == NULL) 
    return SA_ERROR_INVALID;

  /* and this is why allocateBlocks works the way it does */     
  HeapFree(GetProcessHeap(), 0, blocks);
  blocks = NULL;

  return SA_SUCCESS;
}

/** 
 * \brief - open system default WAVE device
 * \param s - sydney audio stream handle
 * \return - completion status
 */ 
int openAudio(sa_stream_t *s) {
  int status;
  WAVEFORMATEX wfx;    
  UINT supported = FALSE;
		  
  status = allocateBlocks(BLOCK_SIZE, BLOCK_COUNT, &(s->waveBlocks));  
	HANDLE_WAVE_ERROR(status, "allocating audio buffer blocks");
  
  s->waveFreeBlockCount	= BLOCK_COUNT;
  s->waveCurrentBlock	= 0;  
  wfx.nSamplesPerSec	= (DWORD)s->rate;	/* sample rate */
  wfx.wBitsPerSample	= 16;				/* sample size */
  wfx.nChannels			= s->channels;	/* channels    */
  wfx.cbSize			= 0;				/* size of _extra_ info */
  wfx.wFormatTag		= WAVE_FORMAT_PCM;
  wfx.nBlockAlign		= (wfx.wBitsPerSample * wfx.nChannels) >> 3;
  wfx.nAvgBytesPerSec	= wfx.nBlockAlign * wfx.nSamplesPerSec;

  supported = waveOutOpen(NULL, WAVE_MAPPER, &wfx, (DWORD_PTR)0, (DWORD_PTR)0, 
				WAVE_FORMAT_QUERY);
  if (supported == MMSYSERR_NOERROR) { // audio device opened sucessfully 
    status = waveOutOpen((LPHWAVEOUT)&(s->hWaveOut), WAVE_MAPPER, &wfx, 
	  (DWORD_PTR)waveOutProc, (DWORD_PTR)s, CALLBACK_FUNCTION);
    HANDLE_WAVE_ERROR(status, "opening audio device for playback");
		printf("Audio device sucessfully opened\n");
  } 
  else if (supported == WAVERR_BADFORMAT) {
    printf("Requested format not supported...\n");
	  // clean up the memory
	  freeBlocks(s->waveBlocks);
    return SA_ERROR_NOT_SUPPORTED;
  } 
  else {
    printf("Error opening default audio device. Exiting...\n");
	  // clean up the memory
	  freeBlocks(s->waveBlocks);
    return SA_ERROR_SYSTEM;
  }
  // create notification for data written to a device
  s->callbackEvent = CreateEvent(0, FALSE, FALSE, 0);
  // initialise critical section for operations on waveFreeBlockCound variable
  InitializeCriticalSection(&(s->waveCriticalSection));

  return SA_SUCCESS;
}
/**
 * \brief - closes opened audio device handle
 * \param s - sydney audio stream handle
 * \return - completion status
 */
int closeAudio(sa_stream_t * s) {
  int status, i;
  
  // reseting audio device and flushing buffers
  status = waveOutReset(s->hWaveOut);    
  HANDLE_WAVE_ERROR(status, "resetting audio device");
  
  /* wait for all blocks to complete */  
  while(s->waveFreeBlockCount < BLOCK_COUNT)
	  Sleep(10);

  /* unprepare any blocks that are still prepared */  
  for(i = 0; i < s->waveFreeBlockCount; i++) {
    if(s->waveBlocks[i].dwFlags & WHDR_PREPARED) {
	    status = waveOutUnprepareHeader(s->hWaveOut, &(s->waveBlocks[i]), sizeof(WAVEHDR));
      HANDLE_WAVE_ERROR(status, "closing audio device");
    }
  }    

  freeBlocks(s->waveBlocks);  
  status = waveOutClose(s->hWaveOut);    
  HANDLE_WAVE_ERROR(status, "closing audio device");

  DeleteCriticalSection(&(s->waveCriticalSection));
  CloseHandle(s->callbackEvent);
  printf("[audio] audio resources cleanup completed\n");
  
  return SA_SUCCESS;
}
/**
 * \brief - writes PCM audio samples to audio device
 * \param s - valid handle to opened sydney stream
 * \param data - pointer to memory storing audio samples to be played
 * \param nsamples - number of samples in the memory pointed by previous parameter
 * \return - completion status
 */
int writeAudio(sa_stream_t *s, LPSTR data, int bytes) {    
  UINT status;
  WAVEHDR* current;	  
  int remain;

  current = &(s->waveBlocks[s->waveCurrentBlock]);
  
  while(bytes > 0) {
    /* first make sure the header we're going to use is unprepared */
    if(current->dwFlags & WHDR_PREPARED) {      
        status = waveOutUnprepareHeader(s->hWaveOut, current, sizeof(WAVEHDR));         
        HANDLE_WAVE_ERROR(status, "preparing audio headers for writing");
    }
		  
    if(bytes < (int)(BLOCK_SIZE - current->dwUser)) {							  	    
		  memcpy(current->lpData + current->dwUser, data, bytes);
      current->dwUser += bytes;
      break;
    }
	
    /* remain is even as BLOCK_SIZE and dwUser are even too */
    remain = BLOCK_SIZE - current->dwUser;      
  	memcpy(current->lpData + current->dwUser, data, remain);
    bytes -= remain;
    data += remain;
	  current->dwBufferLength = BLOCK_SIZE;
	  /* write to audio device */
    waveOutPrepareHeader(s->hWaveOut, current, sizeof(WAVEHDR));
	  status = waveOutWrite(s->hWaveOut, current, sizeof(WAVEHDR));      
    HANDLE_WAVE_ERROR(status, "writing audio to audio device");
      
    EnterCriticalSection(&(s->waveCriticalSection));
    s->waveFreeBlockCount--;
    LeaveCriticalSection(&(s->waveCriticalSection));
    /*
     * wait for a block to become free
     */
    while (!(s->waveFreeBlockCount)) {
        //printf("All audio buffer blocks empty\n");        
      WaitForSingleObject(s->callbackEvent, INFINITE);
        //Sleep(10);
    }		  
		
    /*
     * point to the next block
     */
    (s->waveCurrentBlock)++;
    (s->waveCurrentBlock) %= BLOCK_COUNT;		

    current = &(s->waveBlocks[s->waveCurrentBlock]);
    current->dwUser = 0;
  }
  return SA_SUCCESS;
}

/**
 * \brief - audio callback function called when next WAVE header is played by audio device
 */
void CALLBACK waveOutProc(
    HWAVEOUT hWaveOut, 
    UINT uMsg, 
    DWORD dwInstance,  
    DWORD dwParam1,    
    DWORD dwParam2     
)
{
    /*
     * pointer to free block counter
     */
    sa_stream_t* handle = (sa_stream_t*)dwInstance;
    /*
     * ignore calls that occur due to openining and closing the
     * device.
     */
    if(uMsg != WOM_DONE)
        return;

    EnterCriticalSection(&(handle->waveCriticalSection));
    (handle->waveFreeBlockCount)++;
    if ((handle->waveFreeBlockCount) == 1) 
       SetEvent(handle->callbackEvent);
    LeaveCriticalSection(&(handle->waveCriticalSection));	
}

/**
 * \brief - converts frequently reported WAVE error codes to Sydney audio API codes
 */
int getSAErrorCode(int waveErrorCode) {
  int error = SA_ERROR_NOT_SUPPORTED;

  switch (waveErrorCode) {
    case MMSYSERR_NOERROR: 
      error = SA_SUCCESS;
      break;
    case MMSYSERR_ALLOCATED: 
      error = SA_ERROR_SYSTEM;
      break;
    case MMSYSERR_BADDEVICEID:
      error = SA_ERROR_INVALID;
      break;
    case MMSYSERR_NODRIVER:
      error = SA_ERROR_NO_DRIVER;
      break;
    case MMSYSERR_NOTSUPPORTED:
      error = SA_ERROR_NOT_SUPPORTED;
      break;          
    case MMSYSERR_NOMEM: 
      error = SA_ERROR_OOM;
      break;
    case MMSYSERR_INVALHANDLE:
      error = SA_ERROR_INVALID;
      break;
    case WAVERR_BADFORMAT: 
      error = SA_ERROR_NOT_SUPPORTED;
      break;
    case WAVERR_SYNC: 
      error = SA_ERROR_NOT_SUPPORTED;
      break;    
  }
  return error;
}


/*
 * -----------------------------------------------------------------------------
 * Functions to be implemented next 
 * -----------------------------------------------------------------------------
 */

#define NOT_IMPLEMENTED(func)   func { return SA_ERROR_NOT_SUPPORTED; }

/* "Soft" params */
NOT_IMPLEMENTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
NOT_IMPLEMENTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
NOT_IMPLEMENTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
NOT_IMPLEMENTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))

/** Set the mapping between channels and the loudspeakers */
NOT_IMPLEMENTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))

/* Query functions */
NOT_IMPLEMENTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
NOT_IMPLEMENTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
NOT_IMPLEMENTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
NOT_IMPLEMENTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
NOT_IMPLEMENTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
NOT_IMPLEMENTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
NOT_IMPLEMENTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
NOT_IMPLEMENTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
NOT_IMPLEMENTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
NOT_IMPLEMENTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))

/*
 * -----------------------------------------------------------------------------
 * Unsupported functions
 * -----------------------------------------------------------------------------
 */
#define UNSUPPORTED(func)   func { return SA_ERROR_NOT_SUPPORTED; }

/** Create an opaque (e.g. AC3) codec stream */
UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
/** Whether xruns cause the card to reset */
UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
/** Set the device to non-interleaved mode */
UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
/** Require dynamic sample rate */
UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
/** Select driver */
UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
/** Start callback */
UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
/** Stop callback */
UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
/** Change the device connected to the stream */
UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
/** volume in hundreths of dB*/
UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
/** Change the sampling rate */
UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
/** Change some meta data that is attached to the stream */
UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
/** Associate opaque user data */
UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
/* Hardware-related. This is implementation-specific and hardware specific. */
UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
/* Query functions */
UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))

UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))            
UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
/** Get current state of the audio device */
UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
/** Obtain the error code */
UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
/** Obtain the notification code */
UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))

/* Blocking IO calls */
/** Interleaved capture function */
UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
/** Non-interleaved capture function */
UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))

/** Non-interleaved playback function */
UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
/** Interleaved playback function with seek offset */
UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
/** Non-interleaved playback function with seek offset */
UNSUPPORTED(int sa_stream_pwrite_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))

/** Query how much can be read without blocking */
UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
/** Query how much can be written without blocking */
UNSUPPORTED(int sa_stream_get_write_size(sa_stream_t *s, size_t *size))

/** Block until all audio has been played */
UNSUPPORTED(int sa_stream_drain(sa_stream_t *s))

/** Return a human readable error */
const char *sa_strerror(int code);