MythTV master
audiooutputopensles.cpp
Go to the documentation of this file.
1#include <iostream>
2
3#include <dlfcn.h>
4
5#include <QtGlobal>
6#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
7#include <QAndroidJniEnvironment>
8#else
9#include <QJniEnvironment>
10#include <QJniObject>
11#define QAndroidJniEnvironment QJniEnvironment
12#define QAndroidJniObject QJniObject
13#endif
14
16#include "audiooutputopensles.h"
17
18
19#define LOC QString("AOSLES: ")
20
21#define OPENSLES_BUFFERS 10 /* maximum number of buffers */
22static constexpr std::chrono::milliseconds OPENSLES_BUFLEN { 10ms };
23//#define POSITIONUPDATEPERIOD 1000
24//#define POSITIONUPDATEPERIOD 25000
25#define POSITIONUPDATEPERIOD 40
26//#define POSITIONUPDATEPERIOD 200000
27
28#define CHECK_OPENSL_ERROR(msg) \
29 if (result != SL_RESULT_SUCCESS) \
30 { \
31 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Open Error: (%1)").arg(result)); \
32 Close(); \
33 return false; \
34 }
35
36#define CHECK_OPENSL_START_ERROR(msg) \
37 if (result != SL_RESULT_SUCCESS) \
38 { \
39 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Start Error: (%1)").arg(result)); \
40 Stop(); \
41 return false; \
42 }
43
44
45#define Destroy(a) (*(a))->Destroy(a);
46#define SetPlayState(a, b) (*(a))->SetPlayState(a, b)
47#define RegisterCallback(a, b, c) (*(a))->RegisterCallback(a, b, c)
48#define GetInterface(a, b, c) (*(a))->GetInterface(a, b, c)
49#define Realize(a, b) (*(a))->Realize(a, b)
50#define CreateOutputMix(a, b, c, d, e) (*(a))->CreateOutputMix(a, b, c, d, e)
51#define CreateAudioPlayer(a, b, c, d, e, f, g) \
52 (*(a))->CreateAudioPlayer(a, b, c, d, e, f, g)
53#define Enqueue(a, b, c) (*(a))->Enqueue(a, b, c)
54#define Clear(a) (*(a))->Clear(a)
55#define GetState(a, b) (*(a))->GetState(a, b)
56#define SetPositionUpdatePeriod(a, b) (*(a))->SetPositionUpdatePeriod(a, b)
57#define SetVolumeLevel(a, b) (*(a))->SetVolumeLevel(a, b)
58#define GetVolumeLevel(a, b) (*(a))->GetVolumeLevel(a, b)
59#define SetMute(a, b) (*(a))->SetMute(a, b)
60
62
64{
65#if 1
67 jclass cls = jniEnv->FindClass("android/media/AudioTrack");
68 jmethodID method = jniEnv->GetStaticMethodID (cls, "getNativeOutputSampleRate", "(I)I");
69 int sample_rate = jniEnv->CallStaticIntMethod (cls, method, 3); // AudioManager.STREAM_MUSIC
70 return sample_rate;
71#else
72 //AudioManager.getProperty(PROPERTY_OUTPUT_SAMPLE_RATE)
73 return QAndroidJniObject::callStaticMethodInt("android/media/AudioTrack", "getNativeOutputSampleRate", "(I)I", 3);
74#endif
75}
76
77#if 0
78int GetNativeOutputFramesPerBuffer(void)
79{
80#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
81 QAndroidJniObject activity = QtAndroid::androidActivity();
82#else
83 QJniObject activity = QNativeInterface::QAndroidApplication::context();
84#endif
85 //Context.getSystemService(Context.AUDIO_SERVICE)
86 QAndroidJniObject audioManager = activity.callObjectMethod("getSystemService", "", );
87 jstring sValue = audioManager.callMethod<jstring>("getProperty", PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
88 // getNativeOutputFramesPerBuffer
89 return QAndroidJniObject::callStaticMethodInt("android/media/AudioManager", "getProperty", "(I)I", 3);
90}
91#endif
92
94{
95 SLresult result;
96
97 m_so_handle = dlopen("libOpenSLES.so", RTLD_NOW);
98 if (m_so_handle == nullptr)
99 {
100 LOG(VB_GENERAL, LOG_ERR, LOC + "Error: Failed to load libOpenSLES");
101 Close();
102 return false;
103 }
104
106 if (m_slCreateEnginePtr == nullptr)
107 {
108 LOG(VB_GENERAL, LOG_ERR, LOC + "Error: Failed to load symbol slCreateEngine");
109 Close();
110 return false;
111 }
112
113#define OPENSL_DLSYM(dest, name) \
114 do { \
115 const SLInterfaceID *sym = (const SLInterfaceID *)dlsym(m_so_handle, "SL_IID_" name); \
116 if (sym == nullptr) \
117 { \
118 LOG(VB_GENERAL, LOG_ERR, "AOOSLES Error: Failed to load symbol SL_IID_" name); \
119 Close(); \
120 return false; \
121 } \
122 (dest) = *sym; \
123 } while(0)
124
125 OPENSL_DLSYM(m_SL_IID_ANDROIDSIMPLEBUFFERQUEUE, "ANDROIDSIMPLEBUFFERQUEUE");
126 OPENSL_DLSYM(m_SL_IID_ENGINE, "ENGINE");
128 OPENSL_DLSYM(m_SL_IID_VOLUME, "VOLUME");
129#undef OPENSL_DLSYM
130
131 // create engine
132 result = m_slCreateEnginePtr(&m_engineObject, 0, nullptr, 0, nullptr, nullptr);
133 CHECK_OPENSL_ERROR("Failed to create engine");
134
135 // realize the engine in synchronous mode
136 result = Realize(m_engineObject, SL_BOOLEAN_FALSE);
137 CHECK_OPENSL_ERROR("Failed to realize engine");
138
139 // get the engine interface, needed to create other objects
141 CHECK_OPENSL_ERROR("Failed to get the engine interface");
142
143 // create output mix, with environmental reverb specified as a non-required interface
144 const SLInterfaceID ids1[] = { m_SL_IID_VOLUME };
145 const SLboolean req1[] = { SL_BOOLEAN_FALSE };
146 result = CreateOutputMix(m_engineEngine, &m_outputMixObject, 1, ids1, req1);
147 CHECK_OPENSL_ERROR("Failed to create output mix");
148
149 // realize the output mix in synchronous mode
150 result = Realize(m_outputMixObject, SL_BOOLEAN_FALSE);
151 CHECK_OPENSL_ERROR("Failed to realize output mix");
152
153 return true;
154}
155
157{
158 SLresult result;
159
160 // configure audio source - this defines the number of samples you can enqueue.
161 SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
162 SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
164 };
165
166 SLDataFormat_PCM format_pcm;
167 format_pcm.formatType = SL_DATAFORMAT_PCM;
168 format_pcm.numChannels = 2;
169 format_pcm.samplesPerSec = ((SLuint32) m_sampleRate * 1000) ;
170 format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
171 switch (m_outputFormat)
172 {
173 case FORMAT_U8: format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8; break;
174 case FORMAT_S16: format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; break;
175#if 0
176 case FORMAT_S24LSB: format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_24; break;
177 case FORMAT_S24:
178 format_pcm.endianness = SL_BYTEORDER_BIGENDIAN;
179 format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_24;
180 break;
181 case FORMAT_S32: format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; break;
182 // case FORMAT_FLT: format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; break;
183#endif
184 default:
185 {
186 QString message {QCoreApplication::translate("AudioOutputOpenSLES", "Unknown sample format: %1")
187 .arg(m_outputFormat)};
188 dispatchError(message);
189 LOG(VB_GENERAL, LOG_ERR, message);
190 return false;
191 }
192 }
193 format_pcm.containerSize = format_pcm.bitsPerSample;
194 format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
195
196 SLDataSource audioSrc = {&loc_bufq, &format_pcm};
197
198 // configure audio sink
199 SLDataLocator_OutputMix loc_outmix = {
200 SL_DATALOCATOR_OUTPUTMIX,
202 };
203 SLDataSink audioSnk = {&loc_outmix, nullptr};
204
205 //create audio player
206 const SLInterfaceID ids2[] = { m_SL_IID_ANDROIDSIMPLEBUFFERQUEUE, m_SL_IID_VOLUME };
207 static const SLboolean req2[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
208
209 if (GetNativeOutputSampleRate() >= m_sampleRate) { // FIXME
210 result = CreateAudioPlayer(m_engineEngine, &m_playerObject, &audioSrc,
211 &audioSnk, sizeof(ids2) / sizeof(*ids2),
212 ids2, req2);
213 } else {
214 // Don't try to play back a sample rate higher than the native one,
215 // since OpenSL ES will try to use the fast path, which AudioFlinger
216 // will reject (fast path can't do resampling), and will end up with
217 // too small buffers for the resampling. See http://b.android.com/59453
218 // for details. This bug is still present in 4.4. If it is fixed later
219 // this workaround could be made conditional.
220 result = SL_RESULT_UNKNOWN_ERROR;
221 }
222 if (result != SL_RESULT_SUCCESS) {
223 /* Try again with a more sensible samplerate */
224 //fmt->i_rate = 44100;
225 format_pcm.samplesPerSec = ((SLuint32) 48000 * 1000) ;
226 result = CreateAudioPlayer(m_engineEngine, &m_playerObject, &audioSrc,
227 &audioSnk, sizeof(ids2) / sizeof(*ids2),
228 ids2, req2);
229 }
230 CHECK_OPENSL_ERROR("Failed to create audio player");
231
232 result = Realize(m_playerObject, SL_BOOLEAN_FALSE);
233 CHECK_OPENSL_ERROR("Failed to realize player object.");
234
236 CHECK_OPENSL_ERROR("Failed to get player interface.");
237
239 CHECK_OPENSL_ERROR("failed to get volume interface.");
240
243 CHECK_OPENSL_ERROR("Failed to get buff queue interface");
244
247 (void*)this);
248 CHECK_OPENSL_ERROR("Failed to register buff queue callback.");
249
250 // set the player's state to playing
251 result = SetPlayState(m_playerPlay, SL_PLAYSTATE_PLAYING);
252 CHECK_OPENSL_ERROR("Failed to switch to playing state");
253
254 /* XXX: rounding shouldn't affect us at normal sampling rate */
255 uint32_t samplesPerBuf = OPENSLES_BUFLEN.count() * m_sampleRate / 1000;
256 m_buf = (uint8_t*)malloc(OPENSLES_BUFFERS * samplesPerBuf * m_bytesPerFrame);
257 if (!m_buf)
258 {
259 Stop();
260 return false;
261 }
262
263 m_started = false;
264 m_bufWriteIndex = 0;
265 m_bufWriteBase = 0;
266
267
268 // we want 16bit signed data native endian.
269 //fmt->i_format = VLC_CODEC_S16N;
270 //fmt->i_physical_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT;
271
272/* Buffers which arrive after pts - AOUT_MIN_PREPARE_TIME will be trashed
273 to avoid too heavy resampling */
274 //SetPositionUpdatePeriod(m_playerPlay, 25000);
275 //SetPositionUpdatePeriod(m_playerPlay, 40);
277
278 return true;
279}
280
282{
283 SLresult result;
284 if (m_playerObject)
285 {
286 // set the player's state to playing
287 result = SetPlayState(m_playerPlay, SL_PLAYSTATE_STOPPED);
288 CHECK_OPENSL_ERROR("Failed to switch to not playing state");
290 m_playerObject = nullptr;
291 }
292 if (m_buf)
293 {
294 free(m_buf);
295 m_buf = nullptr;
296 }
297 return true;
298}
299
301{
302 bool result = CreateEngine();
303 if (result)
304 {
305 result = StartPlayer();
306 }
307 if (!result)
308 {
309 Close();
310 }
311 return result;
312}
313
315{
316 Stop();
318 {
320 m_outputMixObject = nullptr;
321 }
322 if (m_engineObject)
323 {
325 m_engineObject = nullptr;
326 }
327 if (m_so_handle)
328 {
330 m_so_handle = nullptr;
331 }
332}
333
334
335
337 AudioOutputBase(settings),
338 m_nativeOutputSampleRate(GetNativeOutputSampleRate())
339{
340 InitSettings(settings);
341 if (settings.m_init)
342 Reconfigure(settings);
343}
344
346{
347 KillAudio();
349}
350
352{
354
355 int32_t nativeRate = GetNativeOutputSampleRate(); // in Hz
356 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Native Rate %1").arg(nativeRate));
357 //settings->AddSupportedRate(48000);
358 settings->AddSupportedRate(nativeRate);
359
360 // Support all standard formats
361 settings->AddSupportedFormat(FORMAT_U8);
363 // settings->AddSupportedFormat(FORMAT_S24);
364 // settings->AddSupportedFormat(FORMAT_S32);
365#if 0 // 32-bit floating point (AC3) is not supported on all platforms.
367#endif
368
369 // Guess that we can do up to 2
370 settings->AddSupportedChannels(2);
371
372 settings->setPassthrough(0); //No passthrough
373
374 return settings;
375}
376
378{
379 CloseDevice();
380
381 if (!Open())
382 {
383 CloseDevice();
384 return false;
385 }
386
387 // fragments are 10ms worth of samples
389 // OpenSLES buffer holds 10 fragments = 80ms worth of samples
391
392 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Buffering %1 fragments of %2 bytes each, total: %3 bytes")
394
395 return true;
396}
397
399{
400 Stop();
401 Close();
402}
403
404void AudioOutputOpenSLES::SPlayedCallback(SLAndroidSimpleBufferQueueItf caller, void *pContext)
405{
406 ((AudioOutputOpenSLES*)pContext)->PlayedCallback(caller);
407}
408
409void AudioOutputOpenSLES::PlayedCallback(SLAndroidSimpleBufferQueueItf caller)
410{
411 assert (caller == m_playerBufferQueue);
412
413 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Freed"));
414
415 m_lock.lock();
416 m_started = true;
417 m_lock.unlock();
418}
419
421{
422 SLAndroidSimpleBufferQueueState st;
423 SLresult res = GetState(m_playerBufferQueue, &st);
424 if (res != SL_RESULT_SUCCESS) {
425 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Could not query buffer queue state in %1 (%2)").arg(__func__).arg(res));
426 return -1;
427 }
428 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Num Queued %1").arg(st.count));
429 return st.count;
430}
431
432void AudioOutputOpenSLES::WriteAudio(unsigned char * buffer, int size)
433{
434 LOG(VB_AUDIO, LOG_INFO, LOC + QString("WriteAudio %1").arg(size));
435 while (size > 0)
436 {
437 // check if there are any buffers available
438 int32_t numBufferesQueued = GetNumberOfBuffersQueued();
439 if (numBufferesQueued < 0)
440 return;
441
442 if (numBufferesQueued == OPENSLES_BUFFERS)
443 {
444 // wait for a buffer period, should be clear next time around
446 continue;
447 }
448
449 if (size < (m_fragmentSize + m_bufWriteIndex))
450 {
451 memcpy(&m_buf[m_bufWriteBase + m_bufWriteIndex], buffer, size);
452 size = 0;
453 // no more to do so exit now, dont have a full buffer
454 break;
455 }
456 else
457 {
460 }
461
463 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Enqueue %1").arg(m_bufWriteBase));
464
465 if (r != SL_RESULT_SUCCESS)
466 {
467 // should never happen so show a log
468 LOG(VB_GENERAL, LOG_ERR, LOC + QString("error %1 when writing %2 bytes %3")
469 .arg(r)
470 .arg(m_fragmentSize)
471 .arg((r == SL_RESULT_BUFFER_INSUFFICIENT) ? " (buffer insufficient)" : ""));
472 // toss the remaining data, we got bigger problems
473 return;
474 }
475 // update pointers for next time only if the data was queued correctly
478 m_bufWriteBase = 0;
479 m_bufWriteIndex = 0;
480 }
481}
482
484{
485 int numBufferesQueued = GetNumberOfBuffersQueued();
486 if (numBufferesQueued < 0)
487 {
488 return 0;
489 }
490 return numBufferesQueued * m_fragmentSize + m_bufWriteIndex;
491}
492
494{
495 SLmillibel mb = 0;
496 int volume = 0;
497 SLresult r = GetVolumeLevel(m_volumeItf, &mb);
498 if (r == SL_RESULT_SUCCESS)
499 {
500 if (mb <= SL_MILLIBEL_MIN)
501 {
502 volume = 0;
503 }
504 else
505 {
506 volume = lroundf(expf(mb / (3*2000.0F)) * 100);
507 }
508 LOG(VB_AUDIO, LOG_INFO, LOC + QString("GetVolume(%1) %2 (%3)")
509 .arg(channel).arg(volume).arg(mb));
510 }
511 else
512 {
513 LOG(VB_GENERAL, LOG_ERR, LOC + QString("GetVolume(%1) %2 (%3) : %4")
514 .arg(channel).arg(volume).arg(mb).arg(r));
515 }
516
517 return volume;
518}
519
520void AudioOutputOpenSLES::SetVolumeChannel(int channel, int volume)
521{
522 if (channel > 1)
523 LOG(VB_GENERAL, LOG_ERR, LOC + "Android volume only supports stereo!");
524
525 // Volume is 0-100
526 // android expects 0-1.0 before conversion
527 // Convert UI volume to linear factor (cube) in log
528 float vol = volume / 100.f;
529
530 int mb;
531 if (volume == 0)
532 {
533 mb = SL_MILLIBEL_MIN;
534 }
535 else
536 {
537 // millibels from linear amplification
538 mb = lroundf(3 * 2000.F * log10f(vol));
539 if (mb < SL_MILLIBEL_MIN)
540 mb = SL_MILLIBEL_MIN;
541 else if (mb > 0)
542 mb = 0; // maximum supported level could be higher: GetMaxVolumeLevel */
543 }
544
545 SLresult r = SetVolumeLevel(m_volumeItf, mb);
546 if (r == SL_RESULT_SUCCESS)
547 {
548 LOG(VB_AUDIO, LOG_INFO, LOC + QString("SetVolume(%1) %2(%3)")
549 .arg(channel).arg(volume).arg(mb));
550 }
551 else
552 {
553 LOG(VB_GENERAL, LOG_ERR, LOC + QString("SetVolume(%1) %2(%3) : %4")
554 .arg(channel).arg(volume).arg(mb).arg(r));
555 }
556
557}
#define assert(x)
#define OPENSLES_BUFFERS
#define QAndroidJniEnvironment
#define LOC
int GetNativeOutputSampleRate(void)
#define SetPositionUpdatePeriod(a, b)
#define GetInterface(a, b, c)
#define CreateAudioPlayer(a, b, c, d, e, f, g)
#define Enqueue(a, b, c)
#define RegisterCallback(a, b, c)
#define Destroy(a)
#define Realize(a, b)
#define GetState(a, b)
#define GetVolumeLevel(a, b)
#define OPENSL_DLSYM(dest, name)
static constexpr std::chrono::milliseconds OPENSLES_BUFLEN
#define CHECK_OPENSL_ERROR(msg)
#define SetVolumeLevel(a, b)
#define POSITIONUPDATEPERIOD
#define QAndroidJniObject
#define SetPlayState(a, b)
#define CreateOutputMix(a, b, c, d, e)
SLresult(*)(SLObjectItf *, SLuint32, const SLEngineOption *, SLuint32, const SLInterfaceID *, const SLboolean *) slCreateEngine_t
@ FORMAT_U8
@ FORMAT_S32
@ FORMAT_S24
@ FORMAT_FLT
@ FORMAT_S16
@ FORMAT_S24LSB
void KillAudio(void)
Kill the output thread and cleanup.
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
AudioFormat m_outputFormat
void InitSettings(const AudioSettings &settings)
static void SPlayedCallback(SLAndroidSimpleBufferQueueItf caller, void *pContext)
void SetVolumeChannel(int channel, int volume) override
void WriteAudio(unsigned char *aubuf, int size) override
SLInterfaceID m_SL_IID_PLAY
slCreateEngine_t m_slCreateEnginePtr
SLInterfaceID m_SL_IID_VOLUME
void PlayedCallback(SLAndroidSimpleBufferQueueItf caller)
AudioOutputSettings * GetOutputSettings(bool digital) override
AudioOutputOpenSLES(const AudioSettings &settings)
SLInterfaceID m_SL_IID_ANDROIDSIMPLEBUFFERQUEUE
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
int GetNumberOfBuffersQueued() const
SLAndroidSimpleBufferQueueItf m_playerBufferQueue
SLInterfaceID m_SL_IID_ENGINE
int GetVolumeChannel(int channel) const override
bool OpenDevice(void) override
void CloseDevice(void) override
void AddSupportedRate(int rate)
void setPassthrough(int val)
void AddSupportedChannels(int channels)
void AddSupportedFormat(AudioFormat format)
void dispatchError(const QString &e)
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
Definition: audiosettings.h:85
static void usleep(std::chrono::microseconds time)
Definition: mthread.cpp:335
#define dlclose(x)
Definition: compat.h:115
#define dlsym(x, y)
Definition: compat.h:116
#define dlopen(x, y)
Definition: compat.h:114
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39