diff --git a/Android.mk b/Android.mk index 2aee63652..0b506320f 100755 --- a/Android.mk +++ b/Android.mk @@ -20,6 +20,7 @@ LOCAL_SRC_FILES := \ $(wildcard $(LOCAL_PATH)/src/audio/*.c) \ $(wildcard $(LOCAL_PATH)/src/audio/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/audio/dummy/*.c) \ + $(wildcard $(LOCAL_PATH)/src/audio/openslES/*.c) \ $(LOCAL_PATH)/src/atomic/SDL_atomic.c.arm \ $(LOCAL_PATH)/src/atomic/SDL_spinlock.c.arm \ $(wildcard $(LOCAL_PATH)/src/core/android/*.c) \ @@ -69,7 +70,7 @@ LOCAL_CFLAGS += \ LOCAL_CFLAGS += -Wno-unused-parameter -Wno-sign-compare -LOCAL_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -llog -landroid +LOCAL_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid ifeq ($(NDK_DEBUG),1) cmd-strip := diff --git a/include/SDL_config_android.h b/include/SDL_config_android.h index 9b80f1522..8f5348605 100644 --- a/include/SDL_config_android.h +++ b/include/SDL_config_android.h @@ -130,6 +130,7 @@ /* Enable various audio drivers */ #define SDL_AUDIO_DRIVER_ANDROID 1 +#define SDL_AUDIO_DRIVER_OPENSLES 1 #define SDL_AUDIO_DRIVER_DUMMY 1 /* Enable various input drivers */ diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 0343052a1..83d3673a0 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -92,6 +92,9 @@ static const AudioBootStrap *const bootstrap[] = { #if SDL_AUDIO_DRIVER_ANDROID &ANDROIDAUDIO_bootstrap, #endif +#if SDL_AUDIO_DRIVER_OPENSLES + &openslES_bootstrap, +#endif #if SDL_AUDIO_DRIVER_PSP &PSPAUDIO_bootstrap, #endif diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index a001efe05..09a77c967 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -205,6 +205,7 @@ extern AudioBootStrap DISKAUDIO_bootstrap; extern AudioBootStrap DUMMYAUDIO_bootstrap; extern AudioBootStrap FUSIONSOUND_bootstrap; extern AudioBootStrap ANDROIDAUDIO_bootstrap; +extern AudioBootStrap openslES_bootstrap; extern AudioBootStrap PSPAUDIO_bootstrap; extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap; diff --git a/src/audio/openslES/SDL_openslES.c b/src/audio/openslES/SDL_openslES.c new file mode 100644 index 000000000..db9f85c21 --- /dev/null +++ b/src/audio/openslES/SDL_openslES.c @@ -0,0 +1,573 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2019 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#if SDL_AUDIO_DRIVER_OPENSLES + +#include "SDL_audio.h" +#include "../SDL_audio_c.h" +#include "SDL_openslES.h" + +// for native audio +#include +#include + +#include + +#define LOG_TAG "SDL_openslES" + +//#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +//#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) +//#define LOGI(...) do {} while (0) +//#define LOGE(...) do {} while (0) +#define LOGI(...) +#define LOGE(...) + +// engine interfaces +static SLObjectItf engineObject = NULL; +static SLEngineItf engineEngine; + +// output mix interfaces +static SLObjectItf outputMixObject = NULL; +//static SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL; + +// aux effect on the output mix, used by the buffer queue player +static const SLEnvironmentalReverbSettings reverbSettings = + SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR; + +// buffer queue player interfaces +static SLObjectItf bqPlayerObject = NULL; +static SLPlayItf bqPlayerPlay; +static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; +//static SLEffectSendItf bqPlayerEffectSend; +static SLMuteSoloItf bqPlayerMuteSolo; +static SLVolumeItf bqPlayerVolume; + +// recorder interfaces TODO +static SLObjectItf recorderObject = NULL; +static SLRecordItf recorderRecord; +static SLAndroidSimpleBufferQueueItf recorderBufferQueue; + +// pointer and size of the next player buffer to enqueue, and number of remaining buffers +static short *nextBuffer; +static unsigned nextSize; +static int nextCount; + +static const char *sldevaudiorecorderstr = "SLES Audio Recorder"; +static const char *sldevaudioplayerstr = "SLES Audio Player"; + +#define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr +#define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr + +#define NUM_BUFFERS 2 /* -- Don't lower this! */ + +static Uint8 *mixbuff = NULL; +static int next_buffer = 0; +static Uint8 *pmixbuff[NUM_BUFFERS]; + +static SDL_sem *playsem = NULL, *recsem = NULL; + +//static SDL_AudioDevice* audioDevice = NULL; + +#if 0 +static void openslES_DetectDevices( int iscapture ) +{ + LOGI( "openSLES_DetectDevices()" ); + if ( iscapture ) + addfn( SLES_DEV_AUDIO_RECORDER ); + else + addfn( SLES_DEV_AUDIO_PLAYER ); + return; +} +#endif + +static void openslES_DestroyEngine( void ); + +static int openslES_CreateEngine( void ) +{ + SLresult result; + + LOGI( "openSLES_CreateEngine()" ); + + // create engine + result = slCreateEngine( &engineObject, 0, NULL, 0, NULL, NULL ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "slCreateEngine failed" ); + goto error; + } + + LOGI( "slCreateEngine OK" ); + + // realize the engine + result = (*engineObject)->Realize( engineObject, SL_BOOLEAN_FALSE ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "RealizeEngine failed" ); + goto error; + } + + LOGI( "RealizeEngine OK" ); + + // get the engine interface, which is needed in order to create other objects + result = (*engineObject)->GetInterface( engineObject, SL_IID_ENGINE, &engineEngine ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "EngineGetInterface failed" ); + goto error; + } + + LOGI( "EngineGetInterface OK" ); + + // create output mix, with environmental reverb specified as a non-required interface +// const SLInterfaceID ids[1] = { SL_IID_ENVIRONMENTALREVERB }; +// const SLboolean req[1] = { SL_BOOLEAN_FALSE }; + + const SLInterfaceID ids[1] = { SL_IID_VOLUME }; + const SLboolean req[1] = { SL_BOOLEAN_FALSE }; + result = (*engineEngine)->CreateOutputMix( engineEngine, &outputMixObject, 1, ids, req ); + + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "CreateOutputMix failed" ); + goto error; + } + LOGI( "CreateOutputMix OK" ); + + // realize the output mix + result = (*outputMixObject)->Realize( outputMixObject, SL_BOOLEAN_FALSE ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "RealizeOutputMix failed" ); + goto error; + } + + return 1; + +error:; + openslES_DestroyEngine( ); + return 0; +} + +static void openslES_DestroyPCMPlayer( void ); +static void openslES_DestroyPCMRecorder( void ); + +static void openslES_DestroyEngine( void ) +{ + LOGI( "openslES_DestroyEngine()" ); + + openslES_DestroyPCMPlayer( ); + openslES_DestroyPCMRecorder( ); + + // destroy output mix object, and invalidate all associated interfaces + if ( outputMixObject != NULL ) { + + (*outputMixObject)->Destroy( outputMixObject ); + outputMixObject = NULL; +// outputMixEnvironmentalReverb = NULL; + } + + // destroy engine object, and invalidate all associated interfaces + if (engineObject != NULL) { + + (*engineObject)->Destroy( engineObject ); + engineObject = NULL; + engineEngine = NULL; + } + + return; +} + +// this callback handler is called every time a buffer finishes playing +static void bqPlayerCallback( SLAndroidSimpleBufferQueueItf bq, void *context ) +{ + static int t = 0; +// assert(bq == bqPlayerBufferQueue); +// assert(NULL == context); + + // for streaming playback, replace this test by logic to find and fill the next buffer +#if 0 + if (--nextCount > 0 && NULL != nextBuffer && 0 != nextSize) { + SLresult result; + // enqueue another buffer + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, nextBuffer, nextSize); + // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, + // which for this code example would indicate a programming error + assert(SL_RESULT_SUCCESS == result); + (void)result; + } +#endif + + LOGI( "SLES: Playback Callmeback %u", t++ ); + + SDL_SemPost( playsem ); + + return; +} + +static int openslES_CreatePCMRecorder( _THIS ) +{ + LOGE( "openslES_CreatePCMRecorder not implimented yet!" ); + return SDL_SetError( "openslES_CreatePCMRecorder not implimented yet!" ); +} + +static void openslES_DestroyPCMRecorder( void ) +{ + return; +} + +static int openslES_CreatePCMPlayer( _THIS ) +{ + SLDataFormat_PCM format_pcm; + SDL_AudioFormat test_format; + SLresult result; + int i; + +/* + test_format = SDL_FirstAudioFormat( this->spec.format ); + + while ( test_format != 0 ) { + + if ( SDL_AUDIO_ISSIGNED(test_format) && SDL_AUDIO_ISINT(test_format ) ) break; + test_format = SDL_NextAudioFormat( ); + } + + if ( test_format == 0 ) { + + // Didn't find a compatible format :( + LOGI( "No compatible audio format!" ); + return SDL_SetError("No compatible audio format!"); + } + + this->spec.format = test_format; +*/ + + // Update the fragment size as size in bytes + SDL_CalculateAudioSpec( &this->spec ); + + LOGI( "Try to open %u hz %u bit chan %u %s samples %u", + this->spec.freq, SDL_AUDIO_BITSIZE( this->spec.format ), + this->spec.channels, (test_format&0x1000) ? "BE" : "LE", this->spec.samples + ); + + // configure audio source + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 }; +// SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, OPENSLES_BUFFERS }; + + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = this->spec.channels; + format_pcm.samplesPerSec = this->spec.freq * 1000; /// kilo Hz to milli Hz + format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE( this->spec.format ); + format_pcm.containerSize = SDL_AUDIO_BITSIZE( this->spec.format ); + + if ( SDL_AUDIO_ISBIGENDIAN( this->spec.format ) ) + format_pcm.endianness = SL_BYTEORDER_BIGENDIAN; + else + format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + +/* +#define SL_SPEAKER_FRONT_LEFT ((SLuint32) 0x00000001) +#define SL_SPEAKER_FRONT_RIGHT ((SLuint32) 0x00000002) +#define SL_SPEAKER_FRONT_CENTER ((SLuint32) 0x00000004) +#define SL_SPEAKER_LOW_FREQUENCY ((SLuint32) 0x00000008) +#define SL_SPEAKER_BACK_LEFT ((SLuint32) 0x00000010) +#define SL_SPEAKER_BACK_RIGHT ((SLuint32) 0x00000020) +#define SL_SPEAKER_FRONT_LEFT_OF_CENTER ((SLuint32) 0x00000040) +#define SL_SPEAKER_FRONT_RIGHT_OF_CENTER ((SLuint32) 0x00000080) +#define SL_SPEAKER_BACK_CENTER ((SLuint32) 0x00000100) +#define SL_SPEAKER_SIDE_LEFT ((SLuint32) 0x00000200) +#define SL_SPEAKER_SIDE_RIGHT ((SLuint32) 0x00000400) +#define SL_SPEAKER_TOP_CENTER ((SLuint32) 0x00000800) +#define SL_SPEAKER_TOP_FRONT_LEFT ((SLuint32) 0x00001000) +#define SL_SPEAKER_TOP_FRONT_CENTER ((SLuint32) 0x00002000) +#define SL_SPEAKER_TOP_FRONT_RIGHT ((SLuint32) 0x00004000) +#define SL_SPEAKER_TOP_BACK_LEFT ((SLuint32) 0x00008000) +#define SL_SPEAKER_TOP_BACK_CENTER ((SLuint32) 0x00010000) +#define SL_SPEAKER_TOP_BACK_RIGHT ((SLuint32) 0x00020000) +*/ + + if ( this->spec.channels == 1 ) + format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER; + else if ( this->spec.channels == 2 ) + format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + else if ( this->spec.channels == 3 ) + format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER; + else if ( this->spec.channels == 4 ) + format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT; + else + format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | + SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT | + SL_SPEAKER_FRONT_CENTER; + + SLDataSource audioSrc = { &loc_bufq, &format_pcm }; + + // configure audio sink + SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject }; + SLDataSink audioSnk = { &loc_outmix, NULL }; + + // create audio player + const SLInterfaceID ids[2] = { + SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_VOLUME + }; + + const SLboolean req[2] = { + SL_BOOLEAN_TRUE, + SL_BOOLEAN_FALSE, + }; + + result = (*engineEngine)->CreateAudioPlayer( engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, + 2, ids, req ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "CreateAudioPlayer failed" ); + goto failed; + } + + // realize the player + result = (*bqPlayerObject)->Realize( bqPlayerObject, SL_BOOLEAN_FALSE ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "RealizeAudioPlayer failed" ); + goto failed; + } + + // get the play interface + result = (*bqPlayerObject)->GetInterface( bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "SL_IID_PLAY interface get failed" ); + goto failed; + } + + // get the buffer queue interface + result = (*bqPlayerObject)->GetInterface( bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "SL_IID_BUFFERQUEUE interface get failed" ); + goto failed; + } + + // register callback on the buffer queue + result = (*bqPlayerBufferQueue)->RegisterCallback( bqPlayerBufferQueue, bqPlayerCallback, NULL ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "RegisterCallback failed" ); + goto failed; + } + +#if 0 + // get the effect send interface + result = (*bqPlayerObject)->GetInterface( bqPlayerObject, SL_IID_EFFECTSEND, &bqPlayerEffectSend ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "SL_IID_EFFECTSEND interface get failed" ); + goto failed; + } +#endif + +#if 0 // mute/solo is not supported for sources that are known to be mono, as this is + // get the mute/solo interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_MUTESOLO, &bqPlayerMuteSolo); + assert(SL_RESULT_SUCCESS == result); + (void)result; +#endif + + // get the volume interface + result = (*bqPlayerObject)->GetInterface( bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "SL_IID_VOLUME interface get failed" ); +// goto failed; + } + + // set the player's state to playing + result = (*bqPlayerPlay)->SetPlayState( bqPlayerPlay, SL_PLAYSTATE_PLAYING ); + if ( SL_RESULT_SUCCESS != result ) { + + LOGE( "Play set state failed" ); + goto failed; + } + + /* Create the audio buffer semaphore */ + playsem = SDL_CreateSemaphore( NUM_BUFFERS - 1 ); + if ( !playsem ) { + + LOGE( "cannot create Semaphore!" ); + goto failed; + } + + /* Create the sound buffers */ + mixbuff = (Uint8 *) SDL_malloc( NUM_BUFFERS * this->spec.size ); + if ( mixbuff == NULL) { + + LOGE( "mixbuffer allocate - out of memory" ); + goto failed; + } + + for ( i = 0; i < NUM_BUFFERS; i ++ ) + pmixbuff[i] = mixbuff + i * this->spec.size; + + return 0; + +failed:; + + openslES_DestroyPCMPlayer( ); + + return SDL_SetError( "Open device failed!" ); +} + +static void openslES_DestroyPCMPlayer( void ) +{ + // destroy buffer queue audio player object, and invalidate all associated interfaces + if ( bqPlayerObject != NULL ) { + + (*bqPlayerObject)->Destroy( bqPlayerObject ); + + bqPlayerObject = NULL; + bqPlayerPlay = NULL; + bqPlayerBufferQueue = NULL; +// bqPlayerEffectSend = NULL; + bqPlayerMuteSolo = NULL; + bqPlayerVolume = NULL; + } + + if ( playsem ) { + + SDL_DestroySemaphore( playsem ); + playsem = NULL; + } + + if ( mixbuff ) + SDL_free( mixbuff ); + + return; +} + +static int openslES_OpenDevice( _THIS, void *handle, const char *devname, int iscapture ) +{ + if ( iscapture ) { + LOGI( "openslES_OpenDevice( ) %s for capture", devname ); + return openslES_CreatePCMRecorder( this ); + } + + LOGI( "openslES_OpenDevice( ) %s for playing", devname ); + + return openslES_CreatePCMPlayer( this ); +} + +static void openslES_CloseDevice( _THIS ) +{ + if ( this->iscapture ) { + LOGI( "openslES_CloseDevice( ) for capture" ); + return openslES_DestroyPCMRecorder( ); + } + + LOGI( "openslES_CloseDevice( ) for playing" ); + openslES_DestroyPCMPlayer( ); + + return; +} + +static void openslES_WaitDevice( _THIS ) +{ + LOGI( "openslES_WaitDevice( )" ); + + /* Wait for an audio chunk to finish */ +// WaitForSingleObject(this->hidden->audio_sem, INFINITE); + SDL_SemWait( playsem ); + + return; +} + +/// n playn sem +// getbuf 0 - 1 +// fill buff 0 - 1 +// play 0 - 0 1 +// wait 1 0 0 +// getbuf 1 0 0 +// fill buff 1 0 0 +// play 0 0 0 +// wait +// +// okay.. + + +static Uint8 *openslES_GetDeviceBuf( _THIS ) +{ + LOGI( "openslES_GetDeviceBuf( )" ); + + return pmixbuff[next_buffer]; +} + +static void openslES_PlayDevice( _THIS ) +{ + SLresult result; + + LOGI( "======openslES_PlayDevice( )======" ); + /* Queue it up */ + + result = (*bqPlayerBufferQueue)->Enqueue( bqPlayerBufferQueue, pmixbuff[next_buffer], this->spec.size ); + if ( SL_RESULT_SUCCESS != result ) { + // just puk here + // next ! + } + + next_buffer ++; + if ( next_buffer >= NUM_BUFFERS ) next_buffer = 0; + + return; +} + +static int openslES_Init( SDL_AudioDriverImpl * impl ) +{ + LOGI( "openslES_Init() called" ); + + if ( !openslES_CreateEngine() ) return 0; + + LOGI( "openslES_Init() - set pointers" ); + + /* Set the function pointers */ +// impl->DetectDevices = openslES_DetectDevices; + impl->OpenDevice = openslES_OpenDevice; + impl->PlayDevice = openslES_PlayDevice; + impl->GetDeviceBuf = openslES_GetDeviceBuf; + impl->Deinitialize = openslES_DestroyEngine; + impl->WaitDevice = openslES_WaitDevice; + + /* and the capabilities */ + impl->HasCaptureSupport = 0; /* TODO */ + impl->OnlyHasDefaultOutputDevice = 1; +// impl->OnlyHasDefaultInputDevice = 1; + + LOGI( "openslES_Init() - succes" ); + + return 1; /* this audio target is available. */ +} + +AudioBootStrap openslES_bootstrap = { + "openslES", "opensl ES audio driver", openslES_Init, 0 +}; + +#endif /* SDL_AUDIO_DRIVER_OPENSLES */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/audio/openslES/SDL_openslES.h b/src/audio/openslES/SDL_openslES.h new file mode 100644 index 000000000..c8f27623f --- /dev/null +++ b/src/audio/openslES/SDL_openslES.h @@ -0,0 +1,42 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2019 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifndef _SDL_openslesaudio_h +#define _SDL_openslesaudio_h + +#include "../SDL_sysaudio.h" + +/* Hidden "this" pointer for the audio functions */ +#define _THIS SDL_AudioDevice *this + +struct SDL_PrivateAudioData +{ + /* The file descriptor for the audio device */ + Uint8 *mixbuf; + Uint32 mixlen; + Uint32 write_delay; + Uint32 initial_calls; +}; + +#endif /* _SDL_openslesaudio_h */ + +/* vi: set ts=4 sw=4 expandtab: */