| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qopenslesaudiooutput.h" |
| #include "qopenslesengine.h" |
| #include <QDebug> |
| #include <qmath.h> |
| |
| #ifdef ANDROID |
| #include <SLES/OpenSLES_Android.h> |
| #include <SLES/OpenSLES_AndroidConfiguration.h> |
| #endif // ANDROID |
| |
| #define BUFFER_COUNT 2 |
| |
| QT_BEGIN_NAMESPACE |
| |
| static inline void openSlDebugInfo() |
| { |
| const QAudioFormat &format = QAudioDeviceInfo::defaultOutputDevice().preferredFormat(); |
| qDebug() << "======= OpenSL ES Device info =======" |
| << "\nSupports low-latency playback: " << (QOpenSLESEngine::supportsLowLatency() ? "YES" : "NO") |
| << "\nPreferred sample rate: " << QOpenSLESEngine::getOutputValue(QOpenSLESEngine::SampleRate, -1) |
| << "\nFrames per buffer: " << QOpenSLESEngine::getOutputValue(QOpenSLESEngine::FramesPerBuffer, -1) |
| << "\nPreferred Format: " << format |
| << "\nLow-latency buffer size: " << QOpenSLESEngine::getLowLatencyBufferSize(format) |
| << "\nDefault buffer size: " << QOpenSLESEngine::getDefaultBufferSize(format); |
| } |
| |
| QMap<QString, qint32> QOpenSLESAudioOutput::m_categories; |
| |
| QOpenSLESAudioOutput::QOpenSLESAudioOutput(const QByteArray &device) |
| : m_deviceName(device), |
| m_state(QAudio::StoppedState), |
| m_error(QAudio::NoError), |
| m_outputMixObject(nullptr), |
| m_playerObject(nullptr), |
| m_playItf(nullptr), |
| m_volumeItf(nullptr), |
| m_bufferQueueItf(nullptr), |
| m_audioSource(nullptr), |
| m_buffers(nullptr), |
| m_volume(1.0), |
| m_pullMode(false), |
| m_nextBuffer(0), |
| m_bufferSize(0), |
| m_notifyInterval(1000), |
| m_periodSize(0), |
| m_elapsedTime(0), |
| m_processedBytes(0), |
| m_availableBuffers(BUFFER_COUNT), |
| m_eventMask(SL_PLAYEVENT_HEADATEND), |
| m_startRequiresInit(true) |
| { |
| #ifndef ANDROID |
| m_streamType = -1; |
| #else |
| m_streamType = SL_ANDROID_STREAM_MEDIA; |
| m_category = QLatin1String("media"); |
| #endif // ANDROID |
| } |
| |
| QOpenSLESAudioOutput::~QOpenSLESAudioOutput() |
| { |
| destroyPlayer(); |
| } |
| |
| QAudio::Error QOpenSLESAudioOutput::error() const |
| { |
| return m_error; |
| } |
| |
| QAudio::State QOpenSLESAudioOutput::state() const |
| { |
| return m_state; |
| } |
| |
| void QOpenSLESAudioOutput::start(QIODevice *device) |
| { |
| Q_ASSERT(device); |
| |
| if (m_state != QAudio::StoppedState) |
| stop(); |
| |
| if (!preparePlayer()) |
| return; |
| |
| m_pullMode = true; |
| m_audioSource = device; |
| m_nextBuffer = 0; |
| m_processedBytes = 0; |
| m_availableBuffers = BUFFER_COUNT; |
| setState(QAudio::ActiveState); |
| setError(QAudio::NoError); |
| |
| // Attempt to fill buffers first. |
| for (int i = 0; i != BUFFER_COUNT; ++i) { |
| const int index = i * m_bufferSize; |
| const qint64 readSize = m_audioSource->read(m_buffers + index, m_bufferSize); |
| if (readSize && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf, |
| m_buffers + index, |
| readSize)) { |
| setError(QAudio::FatalError); |
| destroyPlayer(); |
| return; |
| } |
| m_processedBytes += readSize; |
| } |
| |
| if (m_processedBytes < 1) |
| onEOSEvent(); |
| |
| // Change the state to playing. |
| // We need to do this after filling the buffers or processedBytes might get corrupted. |
| startPlayer(); |
| } |
| |
| QIODevice *QOpenSLESAudioOutput::start() |
| { |
| if (m_state != QAudio::StoppedState) |
| stop(); |
| |
| if (!preparePlayer()) |
| return nullptr; |
| |
| m_pullMode = false; |
| m_processedBytes = 0; |
| m_availableBuffers = BUFFER_COUNT; |
| m_audioSource = new SLIODevicePrivate(this); |
| m_audioSource->open(QIODevice::WriteOnly | QIODevice::Unbuffered); |
| |
| // Change the state to playing |
| startPlayer(); |
| |
| setState(QAudio::IdleState); |
| return m_audioSource; |
| } |
| |
| void QOpenSLESAudioOutput::stop() |
| { |
| if (m_state == QAudio::StoppedState) |
| return; |
| |
| stopPlayer(); |
| setError(QAudio::NoError); |
| } |
| |
| int QOpenSLESAudioOutput::bytesFree() const |
| { |
| if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState) |
| return 0; |
| |
| return m_availableBuffers.loadAcquire() ? m_bufferSize : 0; |
| } |
| |
| int QOpenSLESAudioOutput::periodSize() const |
| { |
| return m_periodSize; |
| } |
| |
| void QOpenSLESAudioOutput::setBufferSize(int value) |
| { |
| if (m_state != QAudio::StoppedState) |
| return; |
| |
| m_startRequiresInit = true; |
| m_bufferSize = value; |
| } |
| |
| int QOpenSLESAudioOutput::bufferSize() const |
| { |
| return m_bufferSize; |
| } |
| |
| void QOpenSLESAudioOutput::setNotifyInterval(int ms) |
| { |
| const int newInterval = ms > 0 ? ms : 0; |
| |
| if (newInterval == m_notifyInterval) |
| return; |
| |
| const SLuint32 newEvenMask = newInterval == 0 ? m_eventMask & ~SL_PLAYEVENT_HEADATNEWPOS |
| : m_eventMask & SL_PLAYEVENT_HEADATNEWPOS; |
| |
| if (m_state == QAudio::StoppedState) { |
| m_eventMask = newEvenMask; |
| m_notifyInterval = newInterval; |
| return; |
| } |
| |
| if (newEvenMask != m_eventMask |
| && SL_RESULT_SUCCESS != (*m_playItf)->SetCallbackEventsMask(m_playItf, newEvenMask)) { |
| return; |
| } |
| |
| m_eventMask = newEvenMask; |
| |
| if (newInterval && SL_RESULT_SUCCESS != (*m_playItf)->SetPositionUpdatePeriod(m_playItf, |
| newInterval)) { |
| return; |
| } |
| |
| m_notifyInterval = newInterval; |
| } |
| |
| int QOpenSLESAudioOutput::notifyInterval() const |
| { |
| return m_notifyInterval; |
| } |
| |
| qint64 QOpenSLESAudioOutput::processedUSecs() const |
| { |
| if (m_state == QAudio::IdleState || m_state == QAudio::SuspendedState) |
| return m_format.durationForBytes(m_processedBytes); |
| |
| SLmillisecond processMSec = 0; |
| if (m_playItf) |
| (*m_playItf)->GetPosition(m_playItf, &processMSec); |
| |
| return processMSec * 1000; |
| } |
| |
| void QOpenSLESAudioOutput::resume() |
| { |
| if (m_state != QAudio::SuspendedState) |
| return; |
| |
| if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) { |
| setError(QAudio::FatalError); |
| destroyPlayer(); |
| return; |
| } |
| |
| setState(m_pullMode ? QAudio::ActiveState : QAudio::IdleState); |
| setError(QAudio::NoError); |
| } |
| |
| void QOpenSLESAudioOutput::setFormat(const QAudioFormat &format) |
| { |
| m_startRequiresInit = true; |
| m_format = format; |
| } |
| |
| QAudioFormat QOpenSLESAudioOutput::format() const |
| { |
| return m_format; |
| } |
| |
| void QOpenSLESAudioOutput::suspend() |
| { |
| if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState) |
| return; |
| |
| if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PAUSED)) { |
| setError(QAudio::FatalError); |
| destroyPlayer(); |
| return; |
| } |
| |
| setState(QAudio::SuspendedState); |
| setError(QAudio::NoError); |
| } |
| |
| qint64 QOpenSLESAudioOutput::elapsedUSecs() const |
| { |
| if (m_state == QAudio::StoppedState) |
| return 0; |
| |
| return m_clockStamp.elapsed() * qint64(1000); |
| } |
| |
| void QOpenSLESAudioOutput::reset() |
| { |
| destroyPlayer(); |
| } |
| |
| void QOpenSLESAudioOutput::setVolume(qreal vol) |
| { |
| m_volume = qBound(qreal(0.0), vol, qreal(1.0)); |
| const SLmillibel newVolume = adjustVolume(m_volume); |
| if (m_volumeItf && SL_RESULT_SUCCESS != (*m_volumeItf)->SetVolumeLevel(m_volumeItf, newVolume)) |
| qWarning() << "Unable to change volume"; |
| } |
| |
| qreal QOpenSLESAudioOutput::volume() const |
| { |
| return m_volume; |
| } |
| |
| void QOpenSLESAudioOutput::setCategory(const QString &category) |
| { |
| #ifndef ANDROID |
| Q_UNUSED(category); |
| #else |
| if (m_categories.isEmpty()) { |
| m_categories.insert(QLatin1String("voice"), SL_ANDROID_STREAM_VOICE); |
| m_categories.insert(QLatin1String("system"), SL_ANDROID_STREAM_SYSTEM); |
| m_categories.insert(QLatin1String("ring"), SL_ANDROID_STREAM_RING); |
| m_categories.insert(QLatin1String("media"), SL_ANDROID_STREAM_MEDIA); |
| m_categories.insert(QLatin1String("alarm"), SL_ANDROID_STREAM_ALARM); |
| m_categories.insert(QLatin1String("notification"), SL_ANDROID_STREAM_NOTIFICATION); |
| } |
| |
| const SLint32 streamType = m_categories.value(category, -1); |
| if (streamType == -1) { |
| qWarning() << "Unknown category" << category |
| << ", available categories are:" << m_categories.keys() |
| << ". Defaulting to category \"media\""; |
| return; |
| } |
| |
| m_startRequiresInit = true; |
| m_streamType = streamType; |
| m_category = category; |
| #endif // ANDROID |
| } |
| |
| QString QOpenSLESAudioOutput::category() const |
| { |
| return m_category; |
| } |
| |
| void QOpenSLESAudioOutput::onEOSEvent() |
| { |
| if (m_state != QAudio::ActiveState) |
| return; |
| |
| SLBufferQueueState state; |
| if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->GetState(m_bufferQueueItf, &state)) |
| return; |
| |
| if (state.count > 0) |
| return; |
| |
| setState(QAudio::IdleState); |
| setError(QAudio::UnderrunError); |
| } |
| |
| void QOpenSLESAudioOutput::onBytesProcessed(qint64 bytes) |
| { |
| m_processedBytes += bytes; |
| } |
| |
| void QOpenSLESAudioOutput::bufferAvailable(quint32 count, quint32 playIndex) |
| { |
| Q_UNUSED(count); |
| Q_UNUSED(playIndex); |
| |
| if (m_state == QAudio::StoppedState) |
| return; |
| |
| if (!m_pullMode) { // We're in push mode. |
| // Signal that there is a new open slot in the buffer and return |
| const int val = m_availableBuffers.fetchAndAddRelease(1) + 1; |
| if (val == BUFFER_COUNT) |
| QMetaObject::invokeMethod(this, "onEOSEvent", Qt::QueuedConnection); |
| |
| return; |
| } |
| |
| // We're in pull mode. |
| const int index = m_nextBuffer * m_bufferSize; |
| const qint64 readSize = m_audioSource->read(m_buffers + index, m_bufferSize); |
| |
| if (readSize < 1) { |
| QMetaObject::invokeMethod(this, "onEOSEvent", Qt::QueuedConnection); |
| return; |
| } |
| |
| |
| if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf, |
| m_buffers + index, |
| readSize)) { |
| setError(QAudio::FatalError); |
| destroyPlayer(); |
| return; |
| } |
| |
| m_nextBuffer = (m_nextBuffer + 1) % BUFFER_COUNT; |
| QMetaObject::invokeMethod(this, "onBytesProcessed", Qt::QueuedConnection, Q_ARG(qint64, readSize)); |
| } |
| |
| void QOpenSLESAudioOutput::playCallback(SLPlayItf player, void *ctx, SLuint32 event) |
| { |
| Q_UNUSED(player); |
| QOpenSLESAudioOutput *audioOutput = reinterpret_cast<QOpenSLESAudioOutput *>(ctx); |
| if (event & SL_PLAYEVENT_HEADATEND) |
| QMetaObject::invokeMethod(audioOutput, "onEOSEvent", Qt::QueuedConnection); |
| if (event & SL_PLAYEVENT_HEADATNEWPOS) |
| Q_EMIT audioOutput->notify(); |
| |
| } |
| |
| void QOpenSLESAudioOutput::bufferQueueCallback(SLBufferQueueItf bufferQueue, void *ctx) |
| { |
| SLBufferQueueState state; |
| (*bufferQueue)->GetState(bufferQueue, &state); |
| QOpenSLESAudioOutput *audioOutput = reinterpret_cast<QOpenSLESAudioOutput *>(ctx); |
| audioOutput->bufferAvailable(state.count, state.playIndex); |
| } |
| |
| bool QOpenSLESAudioOutput::preparePlayer() |
| { |
| if (m_startRequiresInit) |
| destroyPlayer(); |
| else |
| return true; |
| |
| SLEngineItf engine = QOpenSLESEngine::instance()->slEngine(); |
| if (!engine) { |
| qWarning() << "No engine"; |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| SLDataLocator_BufferQueue bufferQueueLocator = { SL_DATALOCATOR_BUFFERQUEUE, BUFFER_COUNT }; |
| SLDataFormat_PCM pcmFormat = QOpenSLESEngine::audioFormatToSLFormatPCM(m_format); |
| |
| SLDataSource audioSrc = { &bufferQueueLocator, &pcmFormat }; |
| |
| // OutputMix |
| if (SL_RESULT_SUCCESS != (*engine)->CreateOutputMix(engine, |
| &m_outputMixObject, |
| 0, |
| nullptr, |
| nullptr)) { |
| qWarning() << "Unable to create output mix"; |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| if (SL_RESULT_SUCCESS != (*m_outputMixObject)->Realize(m_outputMixObject, SL_BOOLEAN_FALSE)) { |
| qWarning() << "Unable to initialize output mix"; |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| SLDataLocator_OutputMix outputMixLocator = { SL_DATALOCATOR_OUTPUTMIX, m_outputMixObject }; |
| SLDataSink audioSink = { &outputMixLocator, nullptr }; |
| |
| #ifndef ANDROID |
| const int iids = 2; |
| const SLInterfaceID ids[iids] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME }; |
| const SLboolean req[iids] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; |
| #else |
| const int iids = 3; |
| const SLInterfaceID ids[iids] = { SL_IID_BUFFERQUEUE, |
| SL_IID_VOLUME, |
| SL_IID_ANDROIDCONFIGURATION }; |
| const SLboolean req[iids] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; |
| #endif // ANDROID |
| |
| // AudioPlayer |
| if (SL_RESULT_SUCCESS != (*engine)->CreateAudioPlayer(engine, |
| &m_playerObject, |
| &audioSrc, |
| &audioSink, |
| iids, |
| ids, |
| req)) { |
| qWarning() << "Unable to create AudioPlayer"; |
| setError(QAudio::OpenError); |
| return false; |
| } |
| |
| #ifdef ANDROID |
| // Set profile/category |
| SLAndroidConfigurationItf playerConfig; |
| if (SL_RESULT_SUCCESS == (*m_playerObject)->GetInterface(m_playerObject, |
| SL_IID_ANDROIDCONFIGURATION, |
| &playerConfig)) { |
| (*playerConfig)->SetConfiguration(playerConfig, |
| SL_ANDROID_KEY_STREAM_TYPE, |
| &m_streamType, |
| sizeof(SLint32)); |
| } |
| #endif // ANDROID |
| |
| if (SL_RESULT_SUCCESS != (*m_playerObject)->Realize(m_playerObject, SL_BOOLEAN_FALSE)) { |
| qWarning() << "Unable to initialize AudioPlayer"; |
| setError(QAudio::OpenError); |
| return false; |
| } |
| |
| // Buffer interface |
| if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject, |
| SL_IID_BUFFERQUEUE, |
| &m_bufferQueueItf)) { |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| if (SL_RESULT_SUCCESS != (*m_bufferQueueItf)->RegisterCallback(m_bufferQueueItf, |
| bufferQueueCallback, |
| this)) { |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| // Play interface |
| if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject, |
| SL_IID_PLAY, |
| &m_playItf)) { |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| if (SL_RESULT_SUCCESS != (*m_playItf)->RegisterCallback(m_playItf, playCallback, this)) { |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| if (m_notifyInterval && SL_RESULT_SUCCESS == (*m_playItf)->SetPositionUpdatePeriod(m_playItf, |
| m_notifyInterval)) { |
| m_eventMask |= SL_PLAYEVENT_HEADATNEWPOS; |
| } |
| |
| if (SL_RESULT_SUCCESS != (*m_playItf)->SetCallbackEventsMask(m_playItf, m_eventMask)) { |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| // Volume interface |
| if (SL_RESULT_SUCCESS != (*m_playerObject)->GetInterface(m_playerObject, |
| SL_IID_VOLUME, |
| &m_volumeItf)) { |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| setVolume(m_volume); |
| |
| const int lowLatencyBufferSize = QOpenSLESEngine::getLowLatencyBufferSize(m_format); |
| const int defaultBufferSize = QOpenSLESEngine::getDefaultBufferSize(m_format); |
| |
| if (defaultBufferSize <= 0) { |
| qWarning() << "Unable to get minimum buffer size, returned" << defaultBufferSize; |
| setError(QAudio::FatalError); |
| return false; |
| } |
| |
| // Buffer size |
| if (m_bufferSize <= 0) { |
| m_bufferSize = defaultBufferSize; |
| } else if (QOpenSLESEngine::supportsLowLatency()) { |
| if (m_bufferSize < lowLatencyBufferSize) |
| m_bufferSize = lowLatencyBufferSize; |
| } else if (m_bufferSize < defaultBufferSize) { |
| m_bufferSize = defaultBufferSize; |
| } |
| |
| m_periodSize = m_bufferSize; |
| |
| if (!m_buffers) |
| m_buffers = new char[BUFFER_COUNT * m_bufferSize]; |
| |
| m_clockStamp.restart(); |
| setError(QAudio::NoError); |
| m_startRequiresInit = false; |
| |
| return true; |
| } |
| |
| void QOpenSLESAudioOutput::destroyPlayer() |
| { |
| if (m_state != QAudio::StoppedState) |
| stopPlayer(); |
| |
| if (m_playerObject) { |
| (*m_playerObject)->Destroy(m_playerObject); |
| m_playerObject = nullptr; |
| } |
| |
| if (m_outputMixObject) { |
| (*m_outputMixObject)->Destroy(m_outputMixObject); |
| m_outputMixObject = nullptr; |
| } |
| |
| if (!m_pullMode && m_audioSource) { |
| m_audioSource->close(); |
| delete m_audioSource; |
| m_audioSource = nullptr; |
| } |
| |
| delete [] m_buffers; |
| m_buffers = nullptr; |
| m_processedBytes = 0; |
| m_nextBuffer = 0; |
| m_availableBuffers.storeRelease(BUFFER_COUNT); |
| m_playItf = nullptr; |
| m_volumeItf = nullptr; |
| m_bufferQueueItf = nullptr; |
| m_startRequiresInit = true; |
| } |
| |
| void QOpenSLESAudioOutput::stopPlayer() |
| { |
| setState(QAudio::StoppedState); |
| |
| if (m_audioSource && !m_pullMode) { |
| m_audioSource->close(); |
| delete m_audioSource; |
| m_audioSource = nullptr; |
| } |
| |
| // We need to change the state manually... |
| if (m_playItf) |
| (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_STOPPED); |
| |
| if (m_bufferQueueItf && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Clear(m_bufferQueueItf)) |
| qWarning() << "Unable to clear buffer"; |
| } |
| |
| void QOpenSLESAudioOutput::startPlayer() |
| { |
| if (QOpenSLESEngine::printDebugInfo()) |
| openSlDebugInfo(); |
| |
| if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) { |
| setError(QAudio::FatalError); |
| destroyPlayer(); |
| } |
| } |
| |
| qint64 QOpenSLESAudioOutput::writeData(const char *data, qint64 len) |
| { |
| if (!len) |
| return 0; |
| |
| if (len > m_bufferSize) |
| len = m_bufferSize; |
| |
| // Acquire one slot in the buffer |
| const int before = m_availableBuffers.fetchAndAddAcquire(-1); |
| |
| // If there where no vacant slots, then we just overdrew the buffer account... |
| if (before < 1) { |
| m_availableBuffers.fetchAndAddRelease(1); |
| return 0; |
| } |
| |
| const int index = m_nextBuffer * m_bufferSize; |
| ::memcpy(m_buffers + index, data, len); |
| const SLuint32 res = (*m_bufferQueueItf)->Enqueue(m_bufferQueueItf, |
| m_buffers + index, |
| len); |
| |
| // If we where unable to enqueue a new buffer, give back the acquired slot. |
| if (res == SL_RESULT_BUFFER_INSUFFICIENT) { |
| m_availableBuffers.fetchAndAddRelease(1); |
| return 0; |
| } |
| |
| if (res != SL_RESULT_SUCCESS) { |
| setError(QAudio::FatalError); |
| destroyPlayer(); |
| return -1; |
| } |
| |
| m_processedBytes += len; |
| setState(QAudio::ActiveState); |
| setError(QAudio::NoError); |
| m_nextBuffer = (m_nextBuffer + 1) % BUFFER_COUNT; |
| |
| return len; |
| } |
| |
| inline void QOpenSLESAudioOutput::setState(QAudio::State state) |
| { |
| if (m_state == state) |
| return; |
| |
| m_state = state; |
| Q_EMIT stateChanged(m_state); |
| } |
| |
| inline void QOpenSLESAudioOutput::setError(QAudio::Error error) |
| { |
| if (m_error == error) |
| return; |
| |
| m_error = error; |
| Q_EMIT errorChanged(m_error); |
| } |
| |
| inline SLmillibel QOpenSLESAudioOutput::adjustVolume(qreal vol) |
| { |
| if (qFuzzyIsNull(vol)) |
| return SL_MILLIBEL_MIN; |
| |
| if (qFuzzyCompare(vol, qreal(1.0))) |
| return 0; |
| |
| return QAudio::convertVolume(vol, QAudio::LinearVolumeScale, QAudio::DecibelVolumeScale) * 100; |
| } |
| |
| QT_END_NAMESPACE |