mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Android: added OpenSLES audio device type.
This commit is contained in:
parent
e6f3788bc3
commit
3a7989ad7c
13 changed files with 681 additions and 24 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -60,4 +60,4 @@ namespace juce
|
|||
#include "synthesisers/juce_Synthesiser.cpp"
|
||||
// END_AUTOINCLUDE
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray <AudioIODeviceType>&
|
|||
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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -123,10 +123,17 @@
|
|||
Juce with low latency audio support, just set the JUCE_JACK flag to 0.
|
||||
*/
|
||||
#include <jack/jack.h>
|
||||
//#include <jack/transport.h>
|
||||
#endif
|
||||
#undef SIZEOF
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_ANDROID
|
||||
|
||||
#if JUCE_USE_ANDROID_OPENSLES
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#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
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
613
modules/juce_audio_devices/native/juce_android_OpenSL.cpp
Normal file
613
modules/juce_audio_devices/native/juce_android_OpenSL.cpp
Normal file
|
|
@ -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> 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> 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<int16> bufferSpace;
|
||||
int nextBlock;
|
||||
Atomic<int> 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 <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> DstSampleType;
|
||||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> 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 <Player*> (context)->playerBufferQueue); (void) queue;
|
||||
static_cast <Player*> (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 <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DstSampleType;
|
||||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const> 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 <Recorder*> (context)->recorderBufferQueue); (void) queue;
|
||||
static_cast <Recorder*> (context)->bufferList.bufferReturned();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Recorder);
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
Engine engine;
|
||||
|
||||
ScopedPointer<Player> player;
|
||||
ScopedPointer<Recorder> 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<OpenSLAudioIODevice> 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;
|
||||
}
|
||||
|
|
@ -189,4 +189,4 @@ namespace juce
|
|||
#include "native/juce_android_Threads.cpp"
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,4 +49,4 @@ namespace juce
|
|||
#include "hashing/juce_SHA256.cpp"
|
||||
// END_AUTOINCLUDE
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,4 +103,4 @@ namespace juce
|
|||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue