diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h index d97026ec10..275c629fbd 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h @@ -388,7 +388,7 @@ private: << newLine << "APP_STL := gnustl_static" << newLine << "APP_CPPFLAGS += -fsigned-char -fexceptions -frtti" << newLine - << "APP_PLATFORM := android-8" << newLine; + << "APP_PLATFORM := " << getAppPlatform() << newLine; overwriteFileIfDifferentOrThrow (file, mo); } @@ -484,6 +484,7 @@ private: { StringPairArray defines; defines.set ("JUCE_ANDROID", "1"); + defines.set ("JUCE_ANDROID_API_VERSION", getMinimumSDKVersionString()); defines.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_')); defines.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\\\"" + getJNIActivityClassName() + "\\\""); @@ -593,13 +594,22 @@ private: equals->setAttribute ("arg2", "debug"); } + String getAppPlatform() const + { + int ndkVersion = getMinimumSDKVersionString().getIntValue(); + if (ndkVersion == 9) + ndkVersion = 10; // (doesn't seem to be a version '9') + + return "android-" + String (ndkVersion); + } + void writeProjectPropertiesFile (const File& file) const { MemoryOutputStream mo; mo << "# This file is used to override default values used by the Ant build system." << newLine << "# It is automatically generated - DO NOT EDIT IT or your changes will be lost!." << newLine << newLine - << "target=Google Inc.:Google APIs:8" << newLine + << "target=" << getAppPlatform() << newLine << newLine; overwriteFileIfDifferentOrThrow (file, mo); diff --git a/modules/juce_audio_basics/juce_audio_basics.cpp b/modules/juce_audio_basics/juce_audio_basics.cpp index 0c17578794..45e87a6cca 100644 --- a/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/modules/juce_audio_basics/juce_audio_basics.cpp @@ -60,4 +60,4 @@ namespace juce #include "synthesisers/juce_Synthesiser.cpp" // END_AUTOINCLUDE -} \ No newline at end of file +} diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 6bdafab088..00f9628a55 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -111,6 +111,7 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray & addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); + addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); } @@ -149,23 +150,24 @@ String AudioDeviceManager::initialise (const int numInputChannelsNeeded, } currentDeviceType = e->getStringAttribute ("deviceType"); - if (currentDeviceType.isEmpty()) + + if (findType (currentDeviceType) == nullptr) { AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName); if (type != nullptr) currentDeviceType = type->getTypeName(); else if (availableDeviceTypes.size() > 0) - currentDeviceType = availableDeviceTypes[0]->getTypeName(); + currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); } setup.bufferSize = e->getIntAttribute ("audioDeviceBufferSize"); setup.sampleRate = e->getDoubleAttribute ("audioDeviceRate"); - setup.inputChannels.parseString (e->getStringAttribute ("audioDeviceInChans", "11"), 2); + setup.inputChannels .parseString (e->getStringAttribute ("audioDeviceInChans", "11"), 2); setup.outputChannels.parseString (e->getStringAttribute ("audioDeviceOutChans", "11"), 2); - setup.useDefaultInputChannels = ! e->hasAttribute ("audioDeviceInChans"); + setup.useDefaultInputChannels = ! e->hasAttribute ("audioDeviceInChans"); setup.useDefaultOutputChannels = ! e->hasAttribute ("audioDeviceOutChans"); error = setAudioDeviceSetup (setup, true); @@ -263,6 +265,17 @@ void AudioDeviceManager::scanDevicesIfNeeded() } } +AudioIODeviceType* AudioDeviceManager::findType (const String& typeName) +{ + scanDevicesIfNeeded(); + + for (int i = availableDeviceTypes.size(); --i >= 0;) + if (availableDeviceTypes.getUnchecked(i)->getTypeName() == typeName) + return availableDeviceTypes.getUnchecked(i); + + return nullptr; +} + AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) { scanDevicesIfNeeded(); diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index fc437904d7..a447fe88a7 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -509,6 +509,7 @@ private: void insertDefaultDeviceNames (AudioDeviceSetup&) const; AudioIODeviceType* findType (const String& inputName, const String& outputName); + AudioIODeviceType* findType (const String& typeName); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceManager); }; diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp index 37b0f27bc8..675fc616a9 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp @@ -74,3 +74,7 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() #if ! JUCE_ANDROID AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } #endif + +#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES) +AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; } +#endif diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h index 6e10b690c5..8929fa9e46 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h @@ -166,6 +166,8 @@ public: static AudioIODeviceType* createAudioIODeviceType_JACK(); /** Creates an Android device type if it's available on this platform, or returns null. */ static AudioIODeviceType* createAudioIODeviceType_Android(); + /** Creates an Android OpenSLES device type if it's available on this platform, or returns null. */ + static AudioIODeviceType* createAudioIODeviceType_OpenSLES(); protected: explicit AudioIODeviceType (const String& typeName); diff --git a/modules/juce_audio_devices/juce_audio_devices.cpp b/modules/juce_audio_devices/juce_audio_devices.cpp index 58ec2dc982..74314f0777 100644 --- a/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/modules/juce_audio_devices/juce_audio_devices.cpp @@ -123,10 +123,17 @@ Juce with low latency audio support, just set the JUCE_JACK flag to 0. */ #include - //#include #endif #undef SIZEOF +//============================================================================== +#elif JUCE_ANDROID + + #if JUCE_USE_ANDROID_OPENSLES + #include + #include + #endif + #endif namespace juce @@ -221,6 +228,10 @@ namespace juce #include "native/juce_android_Audio.cpp" #include "native/juce_android_Midi.cpp" + #if JUCE_USE_ANDROID_OPENSLES + #include "native/juce_android_OpenSL.cpp" + #endif + #endif } diff --git a/modules/juce_audio_devices/juce_audio_devices.h b/modules/juce_audio_devices/juce_audio_devices.h index 81285e9148..b1886d0778 100644 --- a/modules/juce_audio_devices/juce_audio_devices.h +++ b/modules/juce_audio_devices/juce_audio_devices.h @@ -73,6 +73,17 @@ #define JUCE_JACK 0 #endif +/** Config: JUCE_USE_ANDROID_OPENSLES + Enables OpenSLES devices (Android only). +*/ +#ifndef JUCE_USE_ANDROID_OPENSLES + #if JUCE_ANDROID_API_VERSION > 8 + #define JUCE_USE_ANDROID_OPENSLES 1 + #else + #define JUCE_USE_ANDROID_OPENSLES 0 + #endif +#endif + //============================================================================= /** Config: JUCE_USE_CDREADER Enables the AudioCDReader class (on supported platforms). diff --git a/modules/juce_audio_devices/native/juce_android_Audio.cpp b/modules/juce_audio_devices/native/juce_android_Audio.cpp index b97ac389a9..f894150150 100644 --- a/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ b/modules/juce_audio_devices/native/juce_android_Audio.cpp @@ -63,6 +63,8 @@ enum STATE_UNINITIALIZED = 0 }; +const char* const javaAudioTypeName = "Android Audio"; + //============================================================================== class AndroidAudioIODevice : public AudioIODevice, public Thread @@ -70,7 +72,7 @@ class AndroidAudioIODevice : public AudioIODevice, public: //============================================================================== AndroidAudioIODevice (const String& deviceName) - : AudioIODevice (deviceName, "Audio"), + : AudioIODevice (deviceName, javaAudioTypeName), Thread ("audio"), callback (0), sampleRate (0), numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2), @@ -396,24 +398,15 @@ private: class AndroidAudioIODeviceType : public AudioIODeviceType { public: - AndroidAudioIODeviceType() - : AudioIODeviceType ("Android Audio") - { - } + AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {} //============================================================================== void scanForDevices() {} + StringArray getDeviceNames (bool wantInputNames) const { return StringArray (javaAudioTypeName); } int getDefaultDeviceIndex (bool forInput) const { return 0; } int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; } bool hasSeparateInputsAndOutputs() const { return false; } - StringArray getDeviceNames (bool wantInputNames) const - { - StringArray s; - s.add ("Android Audio"); - return s; - } - AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName) { @@ -432,7 +425,6 @@ public: } private: - //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType); }; diff --git a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp new file mode 100644 index 0000000000..980ea46955 --- /dev/null +++ b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -0,0 +1,613 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-11 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online 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.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +const char* const openSLTypeName = "Android OpenSL"; + +//============================================================================== +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) + { + } + + ~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 5;} + double getSampleRate (int index) + { + int rates[] = { 8000, 16000, 32000, 44100, 48000 }; + jassert (index >= 0 && index < numElementsInArray (rates)); + return rates [index]; + } + + int getDefaultBufferSize() { return 2048; } + int getNumBufferSizesAvailable() { return 50; } + + int getBufferSizeSamples (int index) + { + int n = 16; + for (int i = 0; i < index; ++i) + n += n < 64 ? 16 + : (n < 512 ? 32 + : (n < 1024 ? 64 + : (n < 2048 ? 128 : 256))); + + return n; + } + + + 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); + + recorder = engine.createRecorder (numInputChannels, sampleRate); + player = engine.createPlayer (numOutputChannels, sampleRate); + + startThread (8); + + if (recorder != nullptr) recorder->start(); + if (player != nullptr) player->start(); + + deviceOpen = true; + + return lastError; + } + + void close() + { + stop(); + stopThread (2000); + deviceOpen = false; + recorder = nullptr; + player = nullptr; + } + + int getOutputLatencyInSamples() { return 0; } + int getInputLatencyInSamples() { return 0; } + 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() + { + AudioIODeviceCallback* const oldCallback = setCallback (nullptr); + + if (oldCallback != nullptr) + oldCallback->audioDeviceStopped(); + } + + void run() + { + while (! threadShouldExit()) + { + if (recorder != nullptr) + recorder->readNextBlock (inputBuffer, *this); + + invokeCallback(); + + if (player != nullptr && ! threadShouldExit()) + player->writeBuffer (outputBuffer, *this); + } + } + + void invokeCallback() + { + 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; + 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*); + CreateEngineFunc createEngine = (CreateEngineFunc) library.getFunction ("slCreateEngine"); + + if (createEngine != nullptr) + { + 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_, const int numSamples_ = 256, const int numBuffers_ = 16) + : numChannels (numChannels_), numSamples (numSamples_), numBuffers (numBuffers_), + bufferSpace (numChannels_ * numSamples_ * numBuffers_), nextBlock (0) + { + } + + int16* waitForFreeBuffer (Thread& threadToCheck) + { + while (numBlocksOut.get() == numBuffers) + { + Thread::sleep (1); + + if (threadToCheck.threadShouldExit()) + return nullptr; + } + + return getNextBuffer(); + } + + int16* getNextBuffer() + { + if (++nextBlock == numBuffers) + nextBlock = 0; + + return bufferSpace + nextBlock * numChannels * numSamples; + } + + void bufferReturned() { --numBlocksOut; } + void bufferSent() { ++numBlocksOut; } + + int getBufferSizeBytes() const { return numChannels * numSamples * sizeof (int16); } + + const int numChannels, numSamples, numBuffers; + + private: + HeapBlock bufferSpace; + int nextBlock; + Atomic numBlocksOut; + }; + + //================================================================================================== + 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)); + + 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); + + int offset = 0; + int numSamples = buffer.getNumSamples(); + + while (numSamples > 0) + { + int16* const srcBuffer = bufferList.waitForFreeBuffer (thread); + + 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() +{ + DynamicLibrary library; + return library.open ("libOpenSLES.so") ? new OpenSLAudioDeviceType() : nullptr; +} diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index 272404ebd2..375fd34a65 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -189,4 +189,4 @@ namespace juce #include "native/juce_android_Threads.cpp" #endif -} \ No newline at end of file +} diff --git a/modules/juce_cryptography/juce_cryptography.cpp b/modules/juce_cryptography/juce_cryptography.cpp index 44654956cf..da8e2ca3e5 100644 --- a/modules/juce_cryptography/juce_cryptography.cpp +++ b/modules/juce_cryptography/juce_cryptography.cpp @@ -49,4 +49,4 @@ namespace juce #include "hashing/juce_SHA256.cpp" // END_AUTOINCLUDE -} \ No newline at end of file +} diff --git a/modules/juce_events/juce_events.cpp b/modules/juce_events/juce_events.cpp index 89645f267f..15c9337a3b 100644 --- a/modules/juce_events/juce_events.cpp +++ b/modules/juce_events/juce_events.cpp @@ -103,4 +103,4 @@ namespace juce #endif -} \ No newline at end of file +}