/* ***** 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 #include #include #include #include #include // 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);