The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

624 lines
24KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. const char* const openSLTypeName = "Android OpenSL";
  19. bool isOpenSLAvailable()
  20. {
  21. DynamicLibrary library;
  22. return library.open ("libOpenSLES.so");
  23. }
  24. const unsigned short openSLRates[] = { 8000, 16000, 32000, 44100, 48000 };
  25. const unsigned short openSLBufferSizes[] = { 256, 512, 768, 1024, 1280, 1600, 2048, 3072 };
  26. //==============================================================================
  27. class OpenSLAudioIODevice : public AudioIODevice,
  28. public Thread
  29. {
  30. public:
  31. OpenSLAudioIODevice (const String& deviceName)
  32. : AudioIODevice (deviceName, openSLTypeName),
  33. Thread ("OpenSL"),
  34. callback (nullptr), sampleRate (0), deviceOpen (false),
  35. inputBuffer (2, 2), outputBuffer (2, 2)
  36. {
  37. // OpenSL has piss-poor support for determining latency, so the only way I can find to
  38. // get a number for this is by asking the AudioTrack/AudioRecord classes..
  39. AndroidAudioIODevice javaDevice (String::empty);
  40. // this is a total guess about how to calculate this: assuming that internally the
  41. // hardware probably uses 3 buffers, so the latency is about 2/3 of it.. YMMV
  42. inputLatency = (javaDevice.minBufferSizeIn * 2) / 3;
  43. outputLatency = (javaDevice.minBufferSizeOut * 2) / 3;
  44. }
  45. ~OpenSLAudioIODevice()
  46. {
  47. close();
  48. }
  49. bool openedOk() const { return engine.outputMixObject != nullptr; }
  50. StringArray getOutputChannelNames()
  51. {
  52. StringArray s;
  53. s.add ("Left");
  54. s.add ("Right");
  55. return s;
  56. }
  57. StringArray getInputChannelNames()
  58. {
  59. StringArray s;
  60. s.add ("Audio Input");
  61. return s;
  62. }
  63. int getNumSampleRates() { return numElementsInArray (openSLRates); }
  64. double getSampleRate (int index)
  65. {
  66. jassert (index >= 0 && index < getNumSampleRates());
  67. return (int) openSLRates [index];
  68. }
  69. int getDefaultBufferSize() { return 2048; }
  70. int getNumBufferSizesAvailable() { return numElementsInArray (openSLBufferSizes); }
  71. int getBufferSizeSamples (int index)
  72. {
  73. jassert (index >= 0 && index < getNumBufferSizesAvailable());
  74. return (int) openSLBufferSizes [index];
  75. }
  76. String open (const BigInteger& inputChannels,
  77. const BigInteger& outputChannels,
  78. double requestedSampleRate,
  79. int bufferSize)
  80. {
  81. close();
  82. lastError = String::empty;
  83. sampleRate = (int) requestedSampleRate;
  84. int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
  85. activeOutputChans = outputChannels;
  86. activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
  87. numOutputChannels = activeOutputChans.countNumberOfSetBits();
  88. activeInputChans = inputChannels;
  89. activeInputChans.setRange (1, activeInputChans.getHighestBit(), false);
  90. numInputChannels = activeInputChans.countNumberOfSetBits();
  91. actualBufferSize = preferredBufferSize;
  92. inputBuffer.setSize (jmax (1, numInputChannels), actualBufferSize);
  93. outputBuffer.setSize (jmax (1, numOutputChannels), actualBufferSize);
  94. recorder = engine.createRecorder (numInputChannels, sampleRate);
  95. player = engine.createPlayer (numOutputChannels, sampleRate);
  96. startThread (8);
  97. if (recorder != nullptr) recorder->start();
  98. if (player != nullptr) player->start();
  99. deviceOpen = true;
  100. return lastError;
  101. }
  102. void close()
  103. {
  104. stop();
  105. stopThread (2000);
  106. deviceOpen = false;
  107. recorder = nullptr;
  108. player = nullptr;
  109. }
  110. int getOutputLatencyInSamples() { return outputLatency; }
  111. int getInputLatencyInSamples() { return inputLatency; }
  112. bool isOpen() { return deviceOpen; }
  113. int getCurrentBufferSizeSamples() { return actualBufferSize; }
  114. int getCurrentBitDepth() { return 16; }
  115. double getCurrentSampleRate() { return sampleRate; }
  116. BigInteger getActiveOutputChannels() const { return activeOutputChans; }
  117. BigInteger getActiveInputChannels() const { return activeInputChans; }
  118. String getLastError() { return lastError; }
  119. bool isPlaying() { return callback != nullptr; }
  120. void start (AudioIODeviceCallback* newCallback)
  121. {
  122. stop();
  123. if (deviceOpen && callback != newCallback)
  124. {
  125. if (newCallback != nullptr)
  126. newCallback->audioDeviceAboutToStart (this);
  127. setCallback (newCallback);
  128. }
  129. }
  130. void stop()
  131. {
  132. AudioIODeviceCallback* const oldCallback = setCallback (nullptr);
  133. if (oldCallback != nullptr)
  134. oldCallback->audioDeviceStopped();
  135. }
  136. void run()
  137. {
  138. while (! threadShouldExit())
  139. {
  140. if (recorder != nullptr)
  141. recorder->readNextBlock (inputBuffer, *this);
  142. invokeCallback();
  143. if (player != nullptr && ! threadShouldExit())
  144. player->writeBuffer (outputBuffer, *this);
  145. }
  146. }
  147. void invokeCallback()
  148. {
  149. const ScopedLock sl (callbackLock);
  150. if (callback != nullptr)
  151. {
  152. callback->audioDeviceIOCallback (numInputChannels > 0 ? (const float**) inputBuffer.getArrayOfChannels() : nullptr,
  153. numInputChannels,
  154. numOutputChannels > 0 ? outputBuffer.getArrayOfChannels() : nullptr,
  155. numOutputChannels,
  156. actualBufferSize);
  157. }
  158. else
  159. {
  160. outputBuffer.clear();
  161. }
  162. }
  163. private:
  164. //==================================================================================================
  165. CriticalSection callbackLock;
  166. AudioIODeviceCallback* callback;
  167. int actualBufferSize, sampleRate;
  168. int inputLatency, outputLatency;
  169. bool deviceOpen;
  170. String lastError;
  171. BigInteger activeOutputChans, activeInputChans;
  172. int numInputChannels, numOutputChannels;
  173. AudioSampleBuffer inputBuffer, outputBuffer;
  174. struct Player;
  175. struct Recorder;
  176. AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback)
  177. {
  178. const ScopedLock sl (callbackLock);
  179. AudioIODeviceCallback* const oldCallback = callback;
  180. callback = newCallback;
  181. return oldCallback;
  182. }
  183. //==================================================================================================
  184. struct Engine
  185. {
  186. Engine()
  187. : engineObject (nullptr), engineInterface (nullptr), outputMixObject (nullptr)
  188. {
  189. if (library.open ("libOpenSLES.so"))
  190. {
  191. typedef SLresult (*CreateEngineFunc) (SLObjectItf*, SLuint32, const SLEngineOption*, SLuint32, const SLInterfaceID*, const SLboolean*);
  192. CreateEngineFunc createEngine = (CreateEngineFunc) library.getFunction ("slCreateEngine");
  193. if (createEngine != nullptr)
  194. {
  195. check (createEngine (&engineObject, 0, nullptr, 0, nullptr, nullptr));
  196. SLInterfaceID* SL_IID_ENGINE = (SLInterfaceID*) library.getFunction ("SL_IID_ENGINE");
  197. SL_IID_ANDROIDSIMPLEBUFFERQUEUE = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
  198. SL_IID_PLAY = (SLInterfaceID*) library.getFunction ("SL_IID_PLAY");
  199. SL_IID_RECORD = (SLInterfaceID*) library.getFunction ("SL_IID_RECORD");
  200. check ((*engineObject)->Realize (engineObject, SL_BOOLEAN_FALSE));
  201. check ((*engineObject)->GetInterface (engineObject, *SL_IID_ENGINE, &engineInterface));
  202. check ((*engineInterface)->CreateOutputMix (engineInterface, &outputMixObject, 0, nullptr, nullptr));
  203. check ((*outputMixObject)->Realize (outputMixObject, SL_BOOLEAN_FALSE));
  204. }
  205. }
  206. }
  207. ~Engine()
  208. {
  209. if (outputMixObject != nullptr) (*outputMixObject)->Destroy (outputMixObject);
  210. if (engineObject != nullptr) (*engineObject)->Destroy (engineObject);
  211. }
  212. Player* createPlayer (const int numChannels, const int sampleRate)
  213. {
  214. if (numChannels <= 0)
  215. return nullptr;
  216. ScopedPointer<Player> player (new Player (numChannels, sampleRate, *this));
  217. return player->openedOk() ? player.release() : nullptr;
  218. }
  219. Recorder* createRecorder (const int numChannels, const int sampleRate)
  220. {
  221. if (numChannels <= 0)
  222. return nullptr;
  223. ScopedPointer<Recorder> recorder (new Recorder (numChannels, sampleRate, *this));
  224. return recorder->openedOk() ? recorder.release() : nullptr;
  225. }
  226. SLObjectItf engineObject;
  227. SLEngineItf engineInterface;
  228. SLObjectItf outputMixObject;
  229. SLInterfaceID* SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
  230. SLInterfaceID* SL_IID_PLAY;
  231. SLInterfaceID* SL_IID_RECORD;
  232. private:
  233. DynamicLibrary library;
  234. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Engine);
  235. };
  236. //==================================================================================================
  237. struct BufferList
  238. {
  239. BufferList (const int numChannels_, const int numSamples_ = 256, const int numBuffers_ = 16)
  240. : numChannels (numChannels_), numSamples (numSamples_), numBuffers (numBuffers_),
  241. bufferSpace (numChannels_ * numSamples_ * numBuffers_), nextBlock (0)
  242. {
  243. }
  244. int16* waitForFreeBuffer (Thread& threadToCheck)
  245. {
  246. while (numBlocksOut.get() == numBuffers)
  247. {
  248. Thread::sleep (1);
  249. if (threadToCheck.threadShouldExit())
  250. return nullptr;
  251. }
  252. return getNextBuffer();
  253. }
  254. int16* getNextBuffer()
  255. {
  256. if (++nextBlock == numBuffers)
  257. nextBlock = 0;
  258. return bufferSpace + nextBlock * numChannels * numSamples;
  259. }
  260. void bufferReturned() { --numBlocksOut; }
  261. void bufferSent() { ++numBlocksOut; }
  262. int getBufferSizeBytes() const { return numChannels * numSamples * sizeof (int16); }
  263. const int numChannels, numSamples, numBuffers;
  264. private:
  265. HeapBlock<int16> bufferSpace;
  266. int nextBlock;
  267. Atomic<int> numBlocksOut;
  268. };
  269. //==================================================================================================
  270. struct Player
  271. {
  272. Player (int numChannels, int sampleRate, Engine& engine)
  273. : playerObject (nullptr), playerPlay (nullptr), playerBufferQueue (nullptr),
  274. bufferList (numChannels)
  275. {
  276. jassert (numChannels == 2);
  277. SLDataFormat_PCM pcmFormat =
  278. {
  279. SL_DATAFORMAT_PCM,
  280. numChannels,
  281. sampleRate * 1000, // (sample rate units are millihertz)
  282. SL_PCMSAMPLEFORMAT_FIXED_16,
  283. SL_PCMSAMPLEFORMAT_FIXED_16,
  284. SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
  285. SL_BYTEORDER_LITTLEENDIAN
  286. };
  287. SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers };
  288. SLDataSource audioSrc = { &bufferQueue, &pcmFormat };
  289. SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, engine.outputMixObject };
  290. SLDataSink audioSink = { &outputMix, nullptr };
  291. // (SL_IID_BUFFERQUEUE is not guaranteed to remain future-proof, so use SL_IID_ANDROIDSIMPLEBUFFERQUEUE)
  292. const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
  293. const SLboolean flags[] = { SL_BOOLEAN_TRUE };
  294. check ((*engine.engineInterface)->CreateAudioPlayer (engine.engineInterface, &playerObject, &audioSrc, &audioSink,
  295. 1, interfaceIDs, flags));
  296. check ((*playerObject)->Realize (playerObject, SL_BOOLEAN_FALSE));
  297. check ((*playerObject)->GetInterface (playerObject, *engine.SL_IID_PLAY, &playerPlay));
  298. check ((*playerObject)->GetInterface (playerObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &playerBufferQueue));
  299. check ((*playerBufferQueue)->RegisterCallback (playerBufferQueue, staticCallback, this));
  300. }
  301. ~Player()
  302. {
  303. if (playerPlay != nullptr)
  304. check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_STOPPED));
  305. if (playerBufferQueue != nullptr)
  306. check ((*playerBufferQueue)->Clear (playerBufferQueue));
  307. if (playerObject != nullptr)
  308. (*playerObject)->Destroy (playerObject);
  309. }
  310. bool openedOk() const noexcept { return playerBufferQueue != nullptr; }
  311. void start()
  312. {
  313. jassert (openedOk());
  314. check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_PLAYING));
  315. }
  316. void writeBuffer (const AudioSampleBuffer& buffer, Thread& thread)
  317. {
  318. jassert (buffer.getNumChannels() == bufferList.numChannels);
  319. jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers);
  320. int offset = 0;
  321. int numSamples = buffer.getNumSamples();
  322. while (numSamples > 0)
  323. {
  324. int16* const destBuffer = bufferList.waitForFreeBuffer (thread);
  325. if (destBuffer == nullptr)
  326. break;
  327. for (int i = 0; i < bufferList.numChannels; ++i)
  328. {
  329. typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> DstSampleType;
  330. typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SrcSampleType;
  331. DstSampleType dstData (destBuffer + i, bufferList.numChannels);
  332. SrcSampleType srcData (buffer.getSampleData (i, offset));
  333. dstData.convertSamples (srcData, bufferList.numSamples);
  334. }
  335. check ((*playerBufferQueue)->Enqueue (playerBufferQueue, destBuffer, bufferList.getBufferSizeBytes()));
  336. bufferList.bufferSent();
  337. numSamples -= bufferList.numSamples;
  338. offset += bufferList.numSamples;
  339. }
  340. }
  341. private:
  342. SLObjectItf playerObject;
  343. SLPlayItf playerPlay;
  344. SLAndroidSimpleBufferQueueItf playerBufferQueue;
  345. BufferList bufferList;
  346. static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context)
  347. {
  348. jassert (queue == static_cast <Player*> (context)->playerBufferQueue); (void) queue;
  349. static_cast <Player*> (context)->bufferList.bufferReturned();
  350. }
  351. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player);
  352. };
  353. //==================================================================================================
  354. struct Recorder
  355. {
  356. Recorder (int numChannels, int sampleRate, Engine& engine)
  357. : recorderObject (nullptr), recorderRecord (nullptr), recorderBufferQueue (nullptr),
  358. bufferList (numChannels)
  359. {
  360. jassert (numChannels == 1); // STEREO doesn't always work!!
  361. SLDataFormat_PCM pcmFormat =
  362. {
  363. SL_DATAFORMAT_PCM,
  364. numChannels,
  365. sampleRate * 1000, // (sample rate units are millihertz)
  366. SL_PCMSAMPLEFORMAT_FIXED_16,
  367. SL_PCMSAMPLEFORMAT_FIXED_16,
  368. (numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT),
  369. SL_BYTEORDER_LITTLEENDIAN
  370. };
  371. SLDataLocator_IODevice ioDevice = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr };
  372. SLDataSource audioSrc = { &ioDevice, nullptr };
  373. SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers };
  374. SLDataSink audioSink = { &bufferQueue, &pcmFormat };
  375. const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
  376. const SLboolean flags[] = { SL_BOOLEAN_TRUE };
  377. if (check ((*engine.engineInterface)->CreateAudioRecorder (engine.engineInterface, &recorderObject, &audioSrc,
  378. &audioSink, 1, interfaceIDs, flags)))
  379. {
  380. if (check ((*recorderObject)->Realize (recorderObject, SL_BOOLEAN_FALSE)))
  381. {
  382. check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_RECORD, &recorderRecord));
  383. check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue));
  384. check ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this));
  385. for (int i = bufferList.numBuffers; --i >= 0;)
  386. {
  387. int16* const buffer = bufferList.getNextBuffer();
  388. jassert (buffer != nullptr);
  389. enqueueBuffer (buffer);
  390. }
  391. }
  392. }
  393. }
  394. ~Recorder()
  395. {
  396. if (recorderRecord != nullptr)
  397. check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED));
  398. if (recorderBufferQueue != nullptr)
  399. check ((*recorderBufferQueue)->Clear (recorderBufferQueue));
  400. if (recorderObject != nullptr)
  401. (*recorderObject)->Destroy (recorderObject);
  402. }
  403. bool openedOk() const noexcept { return recorderBufferQueue != nullptr; }
  404. void start()
  405. {
  406. jassert (openedOk());
  407. check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_RECORDING));
  408. }
  409. void readNextBlock (AudioSampleBuffer& buffer, Thread& thread)
  410. {
  411. jassert (buffer.getNumChannels() == bufferList.numChannels);
  412. jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers);
  413. int offset = 0;
  414. int numSamples = buffer.getNumSamples();
  415. while (numSamples > 0)
  416. {
  417. int16* const srcBuffer = bufferList.waitForFreeBuffer (thread);
  418. for (int i = 0; i < bufferList.numChannels; ++i)
  419. {
  420. typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DstSampleType;
  421. typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const> SrcSampleType;
  422. DstSampleType dstData (buffer.getSampleData (i, offset));
  423. SrcSampleType srcData (srcBuffer + i, bufferList.numChannels);
  424. dstData.convertSamples (srcData, bufferList.numSamples);
  425. }
  426. enqueueBuffer (srcBuffer);
  427. numSamples -= bufferList.numSamples;
  428. offset += bufferList.numSamples;
  429. }
  430. }
  431. private:
  432. SLObjectItf recorderObject;
  433. SLRecordItf recorderRecord;
  434. SLAndroidSimpleBufferQueueItf recorderBufferQueue;
  435. BufferList bufferList;
  436. void enqueueBuffer (int16* buffer)
  437. {
  438. check ((*recorderBufferQueue)->Enqueue (recorderBufferQueue, buffer, bufferList.getBufferSizeBytes()));
  439. bufferList.bufferSent();
  440. }
  441. static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context)
  442. {
  443. jassert (queue == static_cast <Recorder*> (context)->recorderBufferQueue); (void) queue;
  444. static_cast <Recorder*> (context)->bufferList.bufferReturned();
  445. }
  446. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Recorder);
  447. };
  448. //==============================================================================
  449. Engine engine;
  450. ScopedPointer<Player> player;
  451. ScopedPointer<Recorder> recorder;
  452. //==============================================================================
  453. static bool check (const SLresult result)
  454. {
  455. jassert (result == SL_RESULT_SUCCESS);
  456. return result == SL_RESULT_SUCCESS;
  457. }
  458. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioIODevice);
  459. };
  460. //==============================================================================
  461. class OpenSLAudioDeviceType : public AudioIODeviceType
  462. {
  463. public:
  464. OpenSLAudioDeviceType() : AudioIODeviceType (openSLTypeName) {}
  465. //==============================================================================
  466. void scanForDevices() {}
  467. StringArray getDeviceNames (bool wantInputNames) const { return StringArray (openSLTypeName); }
  468. int getDefaultDeviceIndex (bool forInput) const { return 0; }
  469. int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; }
  470. bool hasSeparateInputsAndOutputs() const { return false; }
  471. AudioIODevice* createDevice (const String& outputDeviceName,
  472. const String& inputDeviceName)
  473. {
  474. ScopedPointer<OpenSLAudioIODevice> dev;
  475. if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
  476. {
  477. dev = new OpenSLAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
  478. : inputDeviceName);
  479. if (! dev->openedOk())
  480. dev = nullptr;
  481. }
  482. return dev.release();
  483. }
  484. private:
  485. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioDeviceType);
  486. };
  487. //==============================================================================
  488. AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES()
  489. {
  490. return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr;
  491. }