/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2013 - Raw Material Software Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ const char* const openSLTypeName = "Android OpenSL"; bool isOpenSLAvailable() { DynamicLibrary library; return library.open ("libOpenSLES.so"); } const unsigned short openSLRates[] = { 8000, 16000, 32000, 44100, 48000 }; const unsigned short openSLBufferSizes[] = { 256, 512, 768, 1024, 1280, 1600 }; // must all be multiples of the block size //============================================================================== class OpenSLAudioIODevice : public AudioIODevice, public Thread { public: OpenSLAudioIODevice (const String& deviceName) : AudioIODevice (deviceName, openSLTypeName), Thread ("OpenSL"), callback (nullptr), sampleRate (0), deviceOpen (false), inputBuffer (2, 2), outputBuffer (2, 2) { // OpenSL has piss-poor support for determining latency, so the only way I can find to // get a number for this is by asking the AudioTrack/AudioRecord classes.. AndroidAudioIODevice javaDevice (String::empty); // this is a total guess about how to calculate the latency, but seems to vaguely agree // with the devices I've tested.. YMMV inputLatency = ((javaDevice.minBufferSizeIn * 2) / 3); outputLatency = ((javaDevice.minBufferSizeOut * 2) / 3); const int longestLatency = jmax (inputLatency, outputLatency); const int totalLatency = inputLatency + outputLatency; inputLatency = ((longestLatency * inputLatency) / totalLatency) & ~15; outputLatency = ((longestLatency * outputLatency) / totalLatency) & ~15; } ~OpenSLAudioIODevice() { close(); } bool openedOk() const { return engine.outputMixObject != nullptr; } StringArray getOutputChannelNames() { StringArray s; s.add ("Left"); s.add ("Right"); return s; } StringArray getInputChannelNames() { StringArray s; s.add ("Audio Input"); return s; } int getNumSampleRates() { return numElementsInArray (openSLRates); } double getSampleRate (int index) { jassert (index >= 0 && index < getNumSampleRates()); return (int) openSLRates [index]; } int getDefaultBufferSize() { return 1024; } int getNumBufferSizesAvailable() { return numElementsInArray (openSLBufferSizes); } int getBufferSizeSamples (int index) { jassert (index >= 0 && index < getNumBufferSizesAvailable()); return (int) openSLBufferSizes [index]; } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, double requestedSampleRate, int bufferSize) { close(); lastError = String::empty; sampleRate = (int) requestedSampleRate; int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; activeOutputChans = outputChannels; activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); numOutputChannels = activeOutputChans.countNumberOfSetBits(); activeInputChans = inputChannels; activeInputChans.setRange (1, activeInputChans.getHighestBit(), false); numInputChannels = activeInputChans.countNumberOfSetBits(); actualBufferSize = preferredBufferSize; inputBuffer.setSize (jmax (1, numInputChannels), actualBufferSize); outputBuffer.setSize (jmax (1, numOutputChannels), actualBufferSize); outputBuffer.clear(); recorder = engine.createRecorder (numInputChannels, sampleRate); player = engine.createPlayer (numOutputChannels, sampleRate); startThread (8); deviceOpen = true; return lastError; } void close() { stop(); stopThread (6000); deviceOpen = false; recorder = nullptr; player = nullptr; } int getOutputLatencyInSamples() { return outputLatency; } int getInputLatencyInSamples() { return inputLatency; } bool isOpen() { return deviceOpen; } int getCurrentBufferSizeSamples() { return actualBufferSize; } int getCurrentBitDepth() { return 16; } double getCurrentSampleRate() { return sampleRate; } BigInteger getActiveOutputChannels() const { return activeOutputChans; } BigInteger getActiveInputChannels() const { return activeInputChans; } String getLastError() { return lastError; } bool isPlaying() { return callback != nullptr; } void start (AudioIODeviceCallback* newCallback) { stop(); if (deviceOpen && callback != newCallback) { if (newCallback != nullptr) newCallback->audioDeviceAboutToStart (this); setCallback (newCallback); } } void stop() { if (AudioIODeviceCallback* const oldCallback = setCallback (nullptr)) oldCallback->audioDeviceStopped(); } void run() override { if (recorder != nullptr) recorder->start(); if (player != nullptr) player->start(); while (! threadShouldExit()) { if (player != nullptr) player->writeBuffer (outputBuffer, *this); if (recorder != nullptr) recorder->readNextBlock (inputBuffer, *this); const ScopedLock sl (callbackLock); if (callback != nullptr) { callback->audioDeviceIOCallback (numInputChannels > 0 ? (const float**) inputBuffer.getArrayOfChannels() : nullptr, numInputChannels, numOutputChannels > 0 ? outputBuffer.getArrayOfChannels() : nullptr, numOutputChannels, actualBufferSize); } else { outputBuffer.clear(); } } } private: //================================================================================================== CriticalSection callbackLock; AudioIODeviceCallback* callback; int actualBufferSize, sampleRate; int inputLatency, outputLatency; bool deviceOpen; String lastError; BigInteger activeOutputChans, activeInputChans; int numInputChannels, numOutputChannels; AudioSampleBuffer inputBuffer, outputBuffer; struct Player; struct Recorder; AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback) { const ScopedLock sl (callbackLock); AudioIODeviceCallback* const oldCallback = callback; callback = newCallback; return oldCallback; } //================================================================================================== struct Engine { Engine() : engineObject (nullptr), engineInterface (nullptr), outputMixObject (nullptr) { if (library.open ("libOpenSLES.so")) { typedef SLresult (*CreateEngineFunc) (SLObjectItf*, SLuint32, const SLEngineOption*, SLuint32, const SLInterfaceID*, const SLboolean*); if (CreateEngineFunc createEngine = (CreateEngineFunc) library.getFunction ("slCreateEngine")) { check (createEngine (&engineObject, 0, nullptr, 0, nullptr, nullptr)); SLInterfaceID* SL_IID_ENGINE = (SLInterfaceID*) library.getFunction ("SL_IID_ENGINE"); SL_IID_ANDROIDSIMPLEBUFFERQUEUE = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); SL_IID_PLAY = (SLInterfaceID*) library.getFunction ("SL_IID_PLAY"); SL_IID_RECORD = (SLInterfaceID*) library.getFunction ("SL_IID_RECORD"); check ((*engineObject)->Realize (engineObject, SL_BOOLEAN_FALSE)); check ((*engineObject)->GetInterface (engineObject, *SL_IID_ENGINE, &engineInterface)); check ((*engineInterface)->CreateOutputMix (engineInterface, &outputMixObject, 0, nullptr, nullptr)); check ((*outputMixObject)->Realize (outputMixObject, SL_BOOLEAN_FALSE)); } } } ~Engine() { if (outputMixObject != nullptr) (*outputMixObject)->Destroy (outputMixObject); if (engineObject != nullptr) (*engineObject)->Destroy (engineObject); } Player* createPlayer (const int numChannels, const int sampleRate) { if (numChannels <= 0) return nullptr; ScopedPointer player (new Player (numChannels, sampleRate, *this)); return player->openedOk() ? player.release() : nullptr; } Recorder* createRecorder (const int numChannels, const int sampleRate) { if (numChannels <= 0) return nullptr; ScopedPointer recorder (new Recorder (numChannels, sampleRate, *this)); return recorder->openedOk() ? recorder.release() : nullptr; } SLObjectItf engineObject; SLEngineItf engineInterface; SLObjectItf outputMixObject; SLInterfaceID* SL_IID_ANDROIDSIMPLEBUFFERQUEUE; SLInterfaceID* SL_IID_PLAY; SLInterfaceID* SL_IID_RECORD; private: DynamicLibrary library; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Engine) }; //================================================================================================== struct BufferList { BufferList (const int numChannels_) : numChannels (numChannels_), bufferSpace (numChannels_ * numSamples * numBuffers), nextBlock (0) { } int16* waitForFreeBuffer (Thread& threadToCheck) { while (numBlocksOut.get() == numBuffers) { dataArrived.wait (1); if (threadToCheck.threadShouldExit()) return nullptr; } return getNextBuffer(); } int16* getNextBuffer() { if (++nextBlock == numBuffers) nextBlock = 0; return bufferSpace + nextBlock * numChannels * numSamples; } void bufferReturned() { --numBlocksOut; dataArrived.signal(); } void bufferSent() { ++numBlocksOut; dataArrived.signal(); } int getBufferSizeBytes() const { return numChannels * numSamples * sizeof (int16); } const int numChannels; enum { numSamples = 256, numBuffers = 16 }; private: HeapBlock bufferSpace; int nextBlock; Atomic numBlocksOut; WaitableEvent dataArrived; }; //================================================================================================== struct Player { Player (int numChannels, int sampleRate, Engine& engine) : playerObject (nullptr), playerPlay (nullptr), playerBufferQueue (nullptr), bufferList (numChannels) { jassert (numChannels == 2); SLDataFormat_PCM pcmFormat = { SL_DATAFORMAT_PCM, numChannels, sampleRate * 1000, // (sample rate units are millihertz) SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN }; SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; SLDataSource audioSrc = { &bufferQueue, &pcmFormat }; SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, engine.outputMixObject }; SLDataSink audioSink = { &outputMix, nullptr }; // (SL_IID_BUFFERQUEUE is not guaranteed to remain future-proof, so use SL_IID_ANDROIDSIMPLEBUFFERQUEUE) const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; const SLboolean flags[] = { SL_BOOLEAN_TRUE }; check ((*engine.engineInterface)->CreateAudioPlayer (engine.engineInterface, &playerObject, &audioSrc, &audioSink, 1, interfaceIDs, flags)); check ((*playerObject)->Realize (playerObject, SL_BOOLEAN_FALSE)); check ((*playerObject)->GetInterface (playerObject, *engine.SL_IID_PLAY, &playerPlay)); check ((*playerObject)->GetInterface (playerObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &playerBufferQueue)); check ((*playerBufferQueue)->RegisterCallback (playerBufferQueue, staticCallback, this)); } ~Player() { if (playerPlay != nullptr) check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_STOPPED)); if (playerBufferQueue != nullptr) check ((*playerBufferQueue)->Clear (playerBufferQueue)); if (playerObject != nullptr) (*playerObject)->Destroy (playerObject); } bool openedOk() const noexcept { return playerBufferQueue != nullptr; } void start() { jassert (openedOk()); check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_PLAYING)); } void writeBuffer (const AudioSampleBuffer& buffer, Thread& thread) { jassert (buffer.getNumChannels() == bufferList.numChannels); jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); int offset = 0; int numSamples = buffer.getNumSamples(); while (numSamples > 0) { int16* const destBuffer = bufferList.waitForFreeBuffer (thread); if (destBuffer == nullptr) break; for (int i = 0; i < bufferList.numChannels; ++i) { typedef AudioData::Pointer DstSampleType; typedef AudioData::Pointer SrcSampleType; DstSampleType dstData (destBuffer + i, bufferList.numChannels); SrcSampleType srcData (buffer.getSampleData (i, offset)); dstData.convertSamples (srcData, bufferList.numSamples); } check ((*playerBufferQueue)->Enqueue (playerBufferQueue, destBuffer, bufferList.getBufferSizeBytes())); bufferList.bufferSent(); numSamples -= bufferList.numSamples; offset += bufferList.numSamples; } } private: SLObjectItf playerObject; SLPlayItf playerPlay; SLAndroidSimpleBufferQueueItf playerBufferQueue; BufferList bufferList; static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) { jassert (queue == static_cast (context)->playerBufferQueue); (void) queue; static_cast (context)->bufferList.bufferReturned(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player) }; //================================================================================================== struct Recorder { Recorder (int numChannels, int sampleRate, Engine& engine) : recorderObject (nullptr), recorderRecord (nullptr), recorderBufferQueue (nullptr), bufferList (numChannels) { jassert (numChannels == 1); // STEREO doesn't always work!! SLDataFormat_PCM pcmFormat = { SL_DATAFORMAT_PCM, numChannels, sampleRate * 1000, // (sample rate units are millihertz) SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, (numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT), SL_BYTEORDER_LITTLEENDIAN }; SLDataLocator_IODevice ioDevice = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; SLDataSource audioSrc = { &ioDevice, nullptr }; SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; SLDataSink audioSink = { &bufferQueue, &pcmFormat }; const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; const SLboolean flags[] = { SL_BOOLEAN_TRUE }; if (check ((*engine.engineInterface)->CreateAudioRecorder (engine.engineInterface, &recorderObject, &audioSrc, &audioSink, 1, interfaceIDs, flags))) { if (check ((*recorderObject)->Realize (recorderObject, SL_BOOLEAN_FALSE))) { check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_RECORD, &recorderRecord)); check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue)); check ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this)); check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); for (int i = bufferList.numBuffers; --i >= 0;) { int16* const buffer = bufferList.getNextBuffer(); jassert (buffer != nullptr); enqueueBuffer (buffer); } } } } ~Recorder() { if (recorderRecord != nullptr) check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); if (recorderBufferQueue != nullptr) check ((*recorderBufferQueue)->Clear (recorderBufferQueue)); if (recorderObject != nullptr) (*recorderObject)->Destroy (recorderObject); } bool openedOk() const noexcept { return recorderBufferQueue != nullptr; } void start() { jassert (openedOk()); check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_RECORDING)); } void readNextBlock (AudioSampleBuffer& buffer, Thread& thread) { jassert (buffer.getNumChannels() == bufferList.numChannels); jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); jassert ((buffer.getNumSamples() % bufferList.numSamples) == 0); int offset = 0; int numSamples = buffer.getNumSamples(); while (numSamples > 0) { int16* const srcBuffer = bufferList.waitForFreeBuffer (thread); if (srcBuffer == nullptr) break; for (int i = 0; i < bufferList.numChannels; ++i) { typedef AudioData::Pointer DstSampleType; typedef AudioData::Pointer SrcSampleType; DstSampleType dstData (buffer.getSampleData (i, offset)); SrcSampleType srcData (srcBuffer + i, bufferList.numChannels); dstData.convertSamples (srcData, bufferList.numSamples); } enqueueBuffer (srcBuffer); numSamples -= bufferList.numSamples; offset += bufferList.numSamples; } } private: SLObjectItf recorderObject; SLRecordItf recorderRecord; SLAndroidSimpleBufferQueueItf recorderBufferQueue; BufferList bufferList; void enqueueBuffer (int16* buffer) { check ((*recorderBufferQueue)->Enqueue (recorderBufferQueue, buffer, bufferList.getBufferSizeBytes())); bufferList.bufferSent(); } static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) { jassert (queue == static_cast (context)->recorderBufferQueue); (void) queue; static_cast (context)->bufferList.bufferReturned(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Recorder) }; //============================================================================== Engine engine; ScopedPointer player; ScopedPointer recorder; //============================================================================== static bool check (const SLresult result) { jassert (result == SL_RESULT_SUCCESS); return result == SL_RESULT_SUCCESS; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioIODevice) }; //============================================================================== class OpenSLAudioDeviceType : public AudioIODeviceType { public: OpenSLAudioDeviceType() : AudioIODeviceType (openSLTypeName) {} //============================================================================== void scanForDevices() {} StringArray getDeviceNames (bool wantInputNames) const { return StringArray (openSLTypeName); } int getDefaultDeviceIndex (bool forInput) const { return 0; } int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; } bool hasSeparateInputsAndOutputs() const { return false; } AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName) { ScopedPointer dev; if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) { dev = new OpenSLAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName); if (! dev->openedOk()) dev = nullptr; } return dev.release(); } private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioDeviceType) }; //============================================================================== AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; }