diff options
author | Lars Knoll <lars.knoll@qt.io> | 2020-12-17 09:59:52 +0100 |
---|---|---|
committer | Lars Knoll <lars.knoll@qt.io> | 2021-01-20 19:13:10 +0000 |
commit | 01ba7c0f49d16042ed4ee5d14c2d543110adc143 (patch) | |
tree | ba39832de60949cc57ba3f8b1c96883db156ea1e /src/multimedia/audio | |
parent | a0d7e8896bfad7eca1c3d393c72ea0f7ebeb79db (diff) |
Move the qnx audio plugin into QtMultimedia
Change-Id: I09825e7d6f1ed724e7238bd167537fcd72b0aeba
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/multimedia/audio')
-rw-r--r-- | src/multimedia/audio/audio.pri | 3 | ||||
-rw-r--r-- | src/multimedia/audio/qaudiosystem.cpp | 4 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnx.pri | 13 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudiodeviceinfo.cpp | 148 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudiodeviceinfo_p.h | 72 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudioinput.cpp | 453 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudioinput_p.h | 138 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudiointerface.cpp | 86 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudiointerface_p.h | 58 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudiooutput.cpp | 578 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudiooutput_p.h | 150 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudioutils.cpp | 128 | ||||
-rw-r--r-- | src/multimedia/audio/qnx/qnxaudioutils_p.h | 55 |
13 files changed, 1884 insertions, 2 deletions
diff --git a/src/multimedia/audio/audio.pri b/src/multimedia/audio/audio.pri index b01110c5e..2ec8b9d9e 100644 --- a/src/multimedia/audio/audio.pri +++ b/src/multimedia/audio/audio.pri @@ -44,11 +44,10 @@ qtConfig(pulseaudio) { SOURCES += audio/qsoundeffect_qaudio_p.cpp } -#qnx:SUBDIRS += qnx-audio - android: include(opensles/opensles.pri) win32: include(windows/windows.pri) darwin:!watchos: include(coreaudio/coreaudio.pri) +qnx: include(qnx/qnx.pri) qtConfig(pulseaudio): include(pulseaudio/pulseaudio.pri) qtConfig(alsa): include(alsa/alsa.pri) diff --git a/src/multimedia/audio/qaudiosystem.cpp b/src/multimedia/audio/qaudiosystem.cpp index 66112a895..ce6d7f656 100644 --- a/src/multimedia/audio/qaudiosystem.cpp +++ b/src/multimedia/audio/qaudiosystem.cpp @@ -50,6 +50,8 @@ #include <private/qwindowsaudiointerface_p.h> #elif defined(Q_OS_ANDROID) #include <private/qopenslesinterface_p.h> +#elif defined(Q_OS_QNX) +#include <private/qnxaudiointerface_p.h> #endif QT_BEGIN_NAMESPACE @@ -437,6 +439,8 @@ QAudioSystemInterface *QAudioSystemInterface::instance() system = new QWindowsAudioInterface(); #elif defined(Q_OS_ANDROID) system = new QOpenSLESAudioInterface(); +#elif defined(Q_OS_QNX) + system = new QnxAudioInterface(); #endif } return system; diff --git a/src/multimedia/audio/qnx/qnx.pri b/src/multimedia/audio/qnx/qnx.pri new file mode 100644 index 000000000..bd583fd54 --- /dev/null +++ b/src/multimedia/audio/qnx/qnx.pri @@ -0,0 +1,13 @@ +LIBS += -lasound + +HEADERS += audio/qnx/qnxaudiointerface_p.h \ + audio/qnx/qnxaudiodeviceinfo_p.h \ + audio/qnx/qnxaudioinput_p.h \ + audio/qnx/qnxaudiooutput_p.h \ + audio/qnx/qnxaudioutils_p.h + +SOURCES += audio/qnx/qnxaudiointerface.cpp \ + audio/qnx/qnxaudiodeviceinfo.cpp \ + audio/qnx/qnxaudioinput.cpp \ + audio/qnx/qnxaudiooutput.cpp \ + audio/qnx/qnxaudioutils.cpp diff --git a/src/multimedia/audio/qnx/qnxaudiodeviceinfo.cpp b/src/multimedia/audio/qnx/qnxaudiodeviceinfo.cpp new file mode 100644 index 000000000..31cf15f9d --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudiodeviceinfo.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "qnxaudiodeviceinfo_p.h" + +#include "qnxaudioutils_p.h" + +#include <sys/asoundlib.h> + +QT_BEGIN_NAMESPACE + +QnxAudioDeviceInfo::QnxAudioDeviceInfo(const QString &deviceName, QAudio::Mode mode) + : m_name(deviceName), + m_mode(mode) +{ +} + +QnxAudioDeviceInfo::~QnxAudioDeviceInfo() +{ +} + +QAudioFormat QnxAudioDeviceInfo::preferredFormat() const +{ + QAudioFormat format; + if (m_mode == QAudio::AudioOutput) { + format.setSampleRate(44100); + format.setChannelCount(2); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleType(QAudioFormat::SignedInt); + format.setSampleSize(16); + format.setCodec(QLatin1String("audio/pcm")); + } else { + format.setSampleRate(8000); + format.setChannelCount(1); + format.setSampleType(QAudioFormat::UnSignedInt); + format.setSampleSize(8); + format.setCodec(QLatin1String("audio/pcm")); + if (!isFormatSupported(format)) { + format.setChannelCount(2); + format.setSampleSize(16); + format.setSampleType(QAudioFormat::SignedInt); + } + } + return format; +} + +bool QnxAudioDeviceInfo::isFormatSupported(const QAudioFormat &format) const +{ + if (!format.codec().startsWith(QLatin1String("audio/pcm"))) + return false; + + const int pcmMode = (m_mode == QAudio::AudioOutput) ? SND_PCM_OPEN_PLAYBACK : SND_PCM_OPEN_CAPTURE; + snd_pcm_t *handle; + + int card = 0; + int device = 0; + if (snd_pcm_open_preferred(&handle, &card, &device, pcmMode) < 0) + return false; + + snd_pcm_channel_info_t info; + memset (&info, 0, sizeof(info)); + info.channel = (m_mode == QAudio::AudioOutput) ? SND_PCM_CHANNEL_PLAYBACK : SND_PCM_CHANNEL_CAPTURE; + + if (snd_pcm_plugin_info(handle, &info) < 0) { + qWarning("QAudioDeviceInfo: couldn't get channel info"); + snd_pcm_close(handle); + return false; + } + + snd_pcm_channel_params_t params = QnxAudioUtils::formatToChannelParams(format, m_mode, info.max_fragment_size); + const int errorCode = snd_pcm_plugin_params(handle, ¶ms); + snd_pcm_close(handle); + + return errorCode == 0; +} + +QString QnxAudioDeviceInfo::deviceName() const +{ + return m_name; +} + +QStringList QnxAudioDeviceInfo::supportedCodecs() +{ + return QStringList() << QLatin1String("audio/pcm"); +} + +QList<int> QnxAudioDeviceInfo::supportedSampleRates() +{ + return QList<int>() << 8000 << 11025 << 22050 << 44100 << 48000; +} + +QList<int> QnxAudioDeviceInfo::supportedChannelCounts() +{ + return QList<int>() << 1 << 2; +} + +QList<int> QnxAudioDeviceInfo::supportedSampleSizes() +{ + return QList<int>() << 8 << 16 << 32; +} + +QList<QAudioFormat::Endian> QnxAudioDeviceInfo::supportedByteOrders() +{ + return QList<QAudioFormat::Endian>() << QAudioFormat::LittleEndian << QAudioFormat::BigEndian; +} + +QList<QAudioFormat::SampleType> QnxAudioDeviceInfo::supportedSampleTypes() +{ + return QList<QAudioFormat::SampleType>() << QAudioFormat::SignedInt << QAudioFormat::UnSignedInt << QAudioFormat::Float; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/audio/qnx/qnxaudiodeviceinfo_p.h b/src/multimedia/audio/qnx/qnxaudiodeviceinfo_p.h new file mode 100644 index 000000000..2d34c0700 --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudiodeviceinfo_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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$ +** +****************************************************************************/ + +#ifndef QNXAUDIODEVICEINFO_H +#define QNXAUDIODEVICEINFO_H + +#include "qaudiosystem_p.h" + +QT_BEGIN_NAMESPACE + +class QnxAudioDeviceInfo : public QAbstractAudioDeviceInfo +{ + Q_OBJECT + +public: + QnxAudioDeviceInfo(const QString &deviceName, QAudio::Mode mode); + ~QnxAudioDeviceInfo(); + + QAudioFormat preferredFormat() const override; + bool isFormatSupported(const QAudioFormat &format) const override; + QString deviceName() const override; + QStringList supportedCodecs() override; + QList<int> supportedSampleRates() override; + QList<int> supportedChannelCounts() override; + QList<int> supportedSampleSizes() override; + QList<QAudioFormat::Endian> supportedByteOrders() override; + QList<QAudioFormat::SampleType> supportedSampleTypes() override; + +private: + const QString m_name; + const QAudio::Mode m_mode; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/audio/qnx/qnxaudioinput.cpp b/src/multimedia/audio/qnx/qnxaudioinput.cpp new file mode 100644 index 000000000..cdf68e5ea --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudioinput.cpp @@ -0,0 +1,453 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "qnxaudioinput_p.h" + +#include "qnxaudioutils_p.h" + +#include <private/qaudiohelpers_p.h> + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +QnxAudioInput::QnxAudioInput() + : m_audioSource(0) + , m_pcmHandle(0) + , m_pcmNotifier(0) + , m_error(QAudio::NoError) + , m_state(QAudio::StoppedState) + , m_bytesRead(0) + , m_elapsedTimeOffset(0) + , m_totalTimeValue(0) + , m_volume(qreal(1.0f)) + , m_bytesAvailable(0) + , m_bufferSize(0) + , m_periodSize(0) + , m_intervalTime(1000) + , m_pullMode(true) +{ +} + +QnxAudioInput::~QnxAudioInput() +{ + close(); +} + +void QnxAudioInput::start(QIODevice *device) +{ + if (m_state != QAudio::StoppedState) + close(); + + if (!m_pullMode && m_audioSource) + delete m_audioSource; + + m_pullMode = true; + m_audioSource = device; + + if (open()) { + setError(QAudio::NoError); + setState(QAudio::ActiveState); + } else { + setError(QAudio::OpenError); + setState(QAudio::StoppedState); + } +} + +QIODevice *QnxAudioInput::start() +{ + if (m_state != QAudio::StoppedState) + close(); + + if (!m_pullMode && m_audioSource) + delete m_audioSource; + + m_pullMode = false; + m_audioSource = new InputPrivate(this); + m_audioSource->open(QIODevice::ReadOnly | QIODevice::Unbuffered); + + if (open()) { + setError(QAudio::NoError); + setState(QAudio::IdleState); + } else { + delete m_audioSource; + m_audioSource = 0; + + setError(QAudio::OpenError); + setState(QAudio::StoppedState); + } + + return m_audioSource; +} + +void QnxAudioInput::stop() +{ + if (m_state == QAudio::StoppedState) + return; + + setError(QAudio::NoError); + setState(QAudio::StoppedState); + close(); +} + +void QnxAudioInput::reset() +{ + stop(); + m_bytesAvailable = 0; +} + +void QnxAudioInput::suspend() +{ + snd_pcm_capture_pause(m_pcmHandle); + + if (m_pcmNotifier) + m_pcmNotifier->setEnabled(false); + + setState(QAudio::SuspendedState); +} + +void QnxAudioInput::resume() +{ + snd_pcm_capture_resume(m_pcmHandle); + + if (m_pcmNotifier) + m_pcmNotifier->setEnabled(true); + + if (m_pullMode) { + setState(QAudio::ActiveState); + } else { + setState(QAudio::IdleState); + } +} + +int QnxAudioInput::bytesReady() const +{ + return qMax(m_bytesAvailable, 0); +} + +int QnxAudioInput::periodSize() const +{ + return m_periodSize; +} + +void QnxAudioInput::setBufferSize(int bufferSize) +{ + m_bufferSize = bufferSize; +} + +int QnxAudioInput::bufferSize() const +{ + return m_bufferSize; +} + +void QnxAudioInput::setNotifyInterval(int milliSeconds) +{ + m_intervalTime = qMax(0, milliSeconds); +} + +int QnxAudioInput::notifyInterval() const +{ + return m_intervalTime; +} + +qint64 QnxAudioInput::processedUSecs() const +{ + return qint64(1000000) * m_format.framesForBytes(m_bytesRead) / m_format.sampleRate(); +} + +qint64 QnxAudioInput::elapsedUSecs() const +{ + if (m_state == QAudio::StoppedState) + return 0; + + return m_clockStamp.elapsed() * qint64(1000); +} + +QAudio::Error QnxAudioInput::error() const +{ + return m_error; +} + +QAudio::State QnxAudioInput::state() const +{ + return m_state; +} + +void QnxAudioInput::setFormat(const QAudioFormat &format) +{ + if (m_state == QAudio::StoppedState) + m_format = format; +} + +QAudioFormat QnxAudioInput::format() const +{ + return m_format; +} + +void QnxAudioInput::setVolume(qreal volume) +{ + m_volume = qBound(qreal(0.0), volume, qreal(1.0)); +} + +qreal QnxAudioInput::volume() const +{ + return m_volume; +} + +void QnxAudioInput::userFeed() +{ + if (m_state == QAudio::StoppedState || m_state == QAudio::SuspendedState) + return; + + deviceReady(); +} + +bool QnxAudioInput::deviceReady() +{ + if (m_pullMode) { + // reads some audio data and writes it to QIODevice + read(0, 0); + } else { + m_bytesAvailable = m_periodSize; + + // emits readyRead() so user will call read() on QIODevice to get some audio data + if (m_audioSource != 0) { + InputPrivate *input = qobject_cast<InputPrivate*>(m_audioSource); + input->trigger(); + } + } + + if (m_state != QAudio::ActiveState) + return true; + + if (m_intervalTime && (m_timeStamp.elapsed() + m_elapsedTimeOffset) > m_intervalTime) { + emit notify(); + m_elapsedTimeOffset = m_timeStamp.elapsed() + m_elapsedTimeOffset - m_intervalTime; + m_timeStamp.restart(); + } + + return true; +} + +bool QnxAudioInput::open() +{ + if (!m_format.isValid() || m_format.sampleRate() <= 0) { + if (!m_format.isValid()) + qWarning("QnxAudioInput: open error, invalid format."); + else + qWarning("QnxAudioInput: open error, invalid sample rate (%d).", m_format.sampleRate()); + + return false; + } + + int errorCode = 0; + + int card = 0; + int device = 0; + if ((errorCode = snd_pcm_open_preferred(&m_pcmHandle, &card, &device, SND_PCM_OPEN_CAPTURE)) < 0) { + qWarning("QnxAudioInput: open error, couldn't open card (0x%x)", -errorCode); + return false; + } + + // Necessary so that bytesFree() which uses the "free" member of the status struct works + snd_pcm_plugin_set_disable(m_pcmHandle, PLUGIN_MMAP); + + snd_pcm_channel_info_t info; + memset(&info, 0, sizeof(info)); + info.channel = SND_PCM_CHANNEL_CAPTURE; + if ((errorCode = snd_pcm_plugin_info(m_pcmHandle, &info)) < 0) { + qWarning("QnxAudioInput: open error, couldn't get channel info (0x%x)", -errorCode); + close(); + return false; + } + + snd_pcm_channel_params_t params = QnxAudioUtils::formatToChannelParams(m_format, QAudio::AudioInput, info.max_fragment_size); + + if ((errorCode = snd_pcm_plugin_params(m_pcmHandle, ¶ms)) < 0) { + qWarning("QnxAudioInput: open error, couldn't set channel params (0x%x)", -errorCode); + close(); + return false; + } + + if ((errorCode = snd_pcm_plugin_prepare(m_pcmHandle, SND_PCM_CHANNEL_CAPTURE)) < 0) { + qWarning("QnxAudioInput: open error, couldn't prepare channel (0x%x)", -errorCode); + close(); + return false; + } + + snd_pcm_channel_setup_t setup; + + memset(&setup, 0, sizeof(setup)); + setup.channel = SND_PCM_CHANNEL_CAPTURE; + if ((errorCode = snd_pcm_plugin_setup(m_pcmHandle, &setup)) < 0) { + qWarning("QnxAudioInput: open error, couldn't get channel setup (0x%x)", -errorCode); + close(); + return false; + } + + m_periodSize = qMin(2048, setup.buf.block.frag_size); + + m_clockStamp.restart(); + m_timeStamp.restart(); + m_elapsedTimeOffset = 0; + m_totalTimeValue = 0; + m_bytesRead = 0; + + m_pcmNotifier = new QSocketNotifier(snd_pcm_file_descriptor(m_pcmHandle, SND_PCM_CHANNEL_CAPTURE), + QSocketNotifier::Read, this); + connect(m_pcmNotifier, SIGNAL(activated(QSocketDescriptor)), SLOT(userFeed())); + + return true; +} + +void QnxAudioInput::close() +{ + if (m_pcmHandle) +#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3,0,2) + snd_pcm_plugin_flush(m_pcmHandle, SND_PCM_CHANNEL_CAPTURE); +#else + snd_pcm_plugin_drop(m_pcmHandle, SND_PCM_CHANNEL_CAPTURE); +#endif + + if (m_pcmNotifier) { + delete m_pcmNotifier; + m_pcmNotifier = 0; + } + + if (m_pcmHandle) { + snd_pcm_close(m_pcmHandle); + m_pcmHandle = 0; + } + + if (!m_pullMode && m_audioSource) { + delete m_audioSource; + m_audioSource = 0; + } +} + +qint64 QnxAudioInput::read(char *data, qint64 len) +{ + int errorCode = 0; + QByteArray tempBuffer(m_periodSize, 0); + + const int actualRead = snd_pcm_plugin_read(m_pcmHandle, tempBuffer.data(), m_periodSize); + if (actualRead < 1) { + snd_pcm_channel_status_t status; + memset(&status, 0, sizeof(status)); + status.channel = SND_PCM_CHANNEL_CAPTURE; + if ((errorCode = snd_pcm_plugin_status(m_pcmHandle, &status)) < 0) { + qWarning("QnxAudioInput: read error, couldn't get plugin status (0x%x)", -errorCode); + close(); + setError(QAudio::FatalError); + setState(QAudio::StoppedState); + return -1; + } + + if (status.status == SND_PCM_STATUS_READY + || status.status == SND_PCM_STATUS_OVERRUN) { + if ((errorCode = snd_pcm_plugin_prepare(m_pcmHandle, SND_PCM_CHANNEL_CAPTURE)) < 0) { + qWarning("QnxAudioInput: read error, couldn't prepare plugin (0x%x)", -errorCode); + close(); + setError(QAudio::FatalError); + setState(QAudio::StoppedState); + return -1; + } + } + } else { + setError(QAudio::NoError); + setState(QAudio::ActiveState); + } + + if (m_volume < 1.0f) + QAudioHelperInternal::qMultiplySamples(m_volume, m_format, tempBuffer.data(), tempBuffer.data(), actualRead); + + m_bytesRead += actualRead; + + if (m_pullMode) { + m_audioSource->write(tempBuffer.data(), actualRead); + } else { + memcpy(data, tempBuffer.data(), qMin(static_cast<qint64>(actualRead), len)); + } + + m_bytesAvailable = 0; + + return actualRead; +} + +void QnxAudioInput::setError(QAudio::Error error) +{ + if (m_error == error) + return; + + m_error = error; + emit errorChanged(m_error); +} + +void QnxAudioInput::setState(QAudio::State state) +{ + if (m_state == state) + return; + + m_state = state; + emit stateChanged(m_state); +} + +InputPrivate::InputPrivate(QnxAudioInput *audio) + : m_audioDevice(audio) +{ +} + +qint64 InputPrivate::readData(char *data, qint64 len) +{ + return m_audioDevice->read(data, len); +} + +qint64 InputPrivate::writeData(const char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + return 0; +} + +void InputPrivate::trigger() +{ + emit readyRead(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/audio/qnx/qnxaudioinput_p.h b/src/multimedia/audio/qnx/qnxaudioinput_p.h new file mode 100644 index 000000000..d0e3ca30b --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudioinput_p.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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$ +** +****************************************************************************/ + +#ifndef QNXAUDIOINPUT_H +#define QNXAUDIOINPUT_H + +#include "qaudiosystem_p.h" + +#include <QSocketNotifier> +#include <QIODevice> +#include <QElapsedTimer> +#include <QTimer> + +#include <sys/asoundlib.h> + +QT_BEGIN_NAMESPACE + +class QnxAudioInput : public QAbstractAudioInput +{ + Q_OBJECT + +public: + QnxAudioInput(); + ~QnxAudioInput(); + + void start(QIODevice*) override; + QIODevice* start() override; + void stop() override; + void reset() override; + void suspend() override; + void resume() override; + int bytesReady() const override; + int periodSize() const override; + void setBufferSize(int ) override; + int bufferSize() const override; + void setNotifyInterval(int ) override; + int notifyInterval() const override; + qint64 processedUSecs() const override; + qint64 elapsedUSecs() const override; + QAudio::Error error() const override; + QAudio::State state() const override; + void setFormat(const QAudioFormat&) override; + QAudioFormat format() const override; + void setVolume(qreal) override; + qreal volume() const override; + +private slots: + void userFeed(); + bool deviceReady(); + +private: + friend class InputPrivate; + + bool open(); + void close(); + qint64 read(char *data, qint64 len); + void setError(QAudio::Error error); + void setState(QAudio::State state); + + QElapsedTimer m_timeStamp; + QElapsedTimer m_clockStamp; + QAudioFormat m_format; + + QIODevice *m_audioSource; + snd_pcm_t *m_pcmHandle; + QSocketNotifier *m_pcmNotifier; + + QAudio::Error m_error; + QAudio::State m_state; + + qint64 m_bytesRead; + qint64 m_elapsedTimeOffset; + qint64 m_totalTimeValue; + + qreal m_volume; + + int m_bytesAvailable; + int m_bufferSize; + int m_periodSize; + int m_intervalTime; + + bool m_pullMode; +}; + +class InputPrivate : public QIODevice +{ + Q_OBJECT +public: + InputPrivate(QnxAudioInput *audio); + + qint64 readData(char *data, qint64 len) override; + qint64 writeData(const char *data, qint64 len) override; + + void trigger(); + +private: + QnxAudioInput *m_audioDevice; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/audio/qnx/qnxaudiointerface.cpp b/src/multimedia/audio/qnx/qnxaudiointerface.cpp new file mode 100644 index 000000000..de3930ee5 --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudiointerface.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "qnxaudiointerface_p.h" + +#include "qnxaudiodeviceinfo_p.h" +#include "qnxaudioinput_p.h" +#include "qnxaudiooutput_p.h" + +#include <sys/asoundlib.h> + +static const char *INPUT_ID = "QnxAudioInput"; +static const char *OUTPUT_ID = "QnxAudioOutput"; + +QT_BEGIN_NAMESPACE + +QByteArray QnxAudioInterface::defaultDevice(QAudio::Mode mode) const +{ + return (mode == QAudio::AudioOutput) ? OUTPUT_ID : INPUT_ID; +} + +QList<QByteArray> QnxAudioInterface::availableDevices(QAudio::Mode mode) const +{ + if (mode == QAudio::AudioOutput) + return QList<QByteArray>() << OUTPUT_ID; + else + return QList<QByteArray>() << INPUT_ID; +} + +QAbstractAudioInput *QnxAudioInterface::createInput(const QByteArray &device) +{ + Q_ASSERT(device == INPUT_ID); + Q_UNUSED(device); + return new QnxAudioInput(); +} + +QAbstractAudioOutput *QnxAudioInterface::createOutput(const QByteArray &device) +{ + Q_ASSERT(device == OUTPUT_ID); + Q_UNUSED(device); + return new QnxAudioOutput(); +} + +QAbstractAudioDeviceInfo *QnxAudioInterface::createDeviceInfo(const QByteArray &device, QAudio::Mode mode) +{ + Q_ASSERT(device == OUTPUT_ID || device == INPUT_ID); + return new QnxAudioDeviceInfo(device, mode); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/audio/qnx/qnxaudiointerface_p.h b/src/multimedia/audio/qnx/qnxaudiointerface_p.h new file mode 100644 index 000000000..8fc50af5f --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudiointerface_p.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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$ +** +****************************************************************************/ + +#ifndef QNXAUDIOPLUGIN_H +#define QNXAUDIOPLUGIN_H + +#include <private/qaudiosystem_p.h> + +QT_BEGIN_NAMESPACE + +class QnxAudioInterface : public QAudioSystemInterface +{ + QByteArray defaultDevice(QAudio::Mode mode) const override; + QList<QByteArray> availableDevices(QAudio::Mode mode) const override; + QAbstractAudioInput *createInput(const QByteArray &device) override; + QAbstractAudioOutput *createOutput(const QByteArray &device) override; + QAbstractAudioDeviceInfo *createDeviceInfo(const QByteArray &device, QAudio::Mode mode) override; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/audio/qnx/qnxaudiooutput.cpp b/src/multimedia/audio/qnx/qnxaudiooutput.cpp new file mode 100644 index 000000000..083b54570 --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudiooutput.cpp @@ -0,0 +1,578 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "qnxaudiooutput_p.h" + +#include "qnxaudioutils_p.h" + +#include <private/qaudiohelpers_p.h> + +#pragma GCC diagnostic ignored "-Wvla" + +QT_BEGIN_NAMESPACE + +QnxAudioOutput::QnxAudioOutput() + : m_source(0) + , m_pushSource(false) + , m_notifyInterval(1000) + , m_error(QAudio::NoError) + , m_state(QAudio::StoppedState) + , m_volume(1.0) + , m_periodSize(0) + , m_pcmHandle(0) + , m_bytesWritten(0) + , m_intervalOffset(0) +#if _NTO_VERSION >= 700 + , m_pcmNotifier(0) +#endif +{ + m_timer.setSingleShot(false); + m_timer.setInterval(20); + connect(&m_timer, SIGNAL(timeout()), this, SLOT(pullData())); +} + +QnxAudioOutput::~QnxAudioOutput() +{ + stop(); +} + +void QnxAudioOutput::start(QIODevice *source) +{ + if (m_state != QAudio::StoppedState) + stop(); + + m_error = QAudio::NoError; + m_source = source; + m_pushSource = false; + + if (open()) { + setState(QAudio::ActiveState); + m_timer.start(); + } else { + setError(QAudio::OpenError); + setState(QAudio::StoppedState); + } +} + +QIODevice *QnxAudioOutput::start() +{ + if (m_state != QAudio::StoppedState) + stop(); + + m_error = QAudio::NoError; + m_source = new QnxPushIODevice(this); + m_source->open(QIODevice::WriteOnly|QIODevice::Unbuffered); + m_pushSource = true; + + if (open()) + setState(QAudio::IdleState); + else { + setError(QAudio::OpenError); + setState(QAudio::StoppedState); + } + + return m_source; +} + +void QnxAudioOutput::stop() +{ + if (m_state == QAudio::StoppedState) + return; + + setError(QAudio::NoError); + setState(QAudio::StoppedState); + close(); +} + +void QnxAudioOutput::reset() +{ + if (m_pcmHandle) +#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3,0,2) + snd_pcm_playback_drain(m_pcmHandle); +#else + snd_pcm_channel_drain(m_pcmHandle, SND_PCM_CHANNEL_PLAYBACK); +#endif + stop(); +} + +void QnxAudioOutput::suspend() +{ + snd_pcm_playback_pause(m_pcmHandle); + if (state() != QAudio::InterruptedState) + suspendInternal(QAudio::SuspendedState); +} + +void QnxAudioOutput::resume() +{ + snd_pcm_playback_resume(m_pcmHandle); + if (state() != QAudio::InterruptedState) + resumeInternal(); +} + +int QnxAudioOutput::bytesFree() const +{ + if (m_state != QAudio::ActiveState && m_state != QAudio::IdleState) + return 0; + + snd_pcm_channel_status_t status; + memset(&status, 0, sizeof(status)); + status.channel = SND_PCM_CHANNEL_PLAYBACK; + const int errorCode = snd_pcm_plugin_status(m_pcmHandle, &status); + + if (errorCode) + return 0; + else + return status.free; +} + +int QnxAudioOutput::periodSize() const +{ + return m_periodSize; +} + +void QnxAudioOutput::setNotifyInterval(int ms) +{ + m_notifyInterval = ms; +} + +int QnxAudioOutput::notifyInterval() const +{ + return m_notifyInterval; +} + +qint64 QnxAudioOutput::processedUSecs() const +{ + return qint64(1000000) * m_format.framesForBytes(m_bytesWritten) / m_format.sampleRate(); +} + +qint64 QnxAudioOutput::elapsedUSecs() const +{ + if (m_state == QAudio::StoppedState) + return 0; + else + return m_startTimeStamp.elapsed() * qint64(1000); +} + +QAudio::Error QnxAudioOutput::error() const +{ + return m_error; +} + +QAudio::State QnxAudioOutput::state() const +{ + return m_state; +} + +void QnxAudioOutput::setFormat(const QAudioFormat &format) +{ + if (m_state == QAudio::StoppedState) + m_format = format; +} + +QAudioFormat QnxAudioOutput::format() const +{ + return m_format; +} + +void QnxAudioOutput::setVolume(qreal volume) +{ + m_volume = qBound(qreal(0.0), volume, qreal(1.0)); +} + +qreal QnxAudioOutput::volume() const +{ + return m_volume; +} + +void QnxAudioOutput::setCategory(const QString &category) +{ + m_category = category; +} + +QString QnxAudioOutput::category() const +{ + return m_category; +} + +void QnxAudioOutput::pullData() +{ + if (m_state == QAudio::StoppedState + || m_state == QAudio::SuspendedState + || m_state == QAudio::InterruptedState) + return; + + const int bytesAvailable = bytesFree(); + const int frames = m_format.framesForBytes(bytesAvailable); + + if (frames == 0 || bytesAvailable < periodSize()) + return; + + // The buffer is placed on the stack so no more than 64K or 1 frame + // whichever is larger. + const int maxFrames = qMax(m_format.framesForBytes(64 * 1024), 1); + const int bytesRequested = m_format.bytesForFrames(qMin(frames, maxFrames)); + + char buffer[bytesRequested]; + const int bytesRead = m_source->read(buffer, bytesRequested); + + // reading can take a while and stream may have been stopped + if (!m_pcmHandle) + return; + + if (bytesRead > 0) { + // Got some data to output + if (m_state != QAudio::ActiveState) + return; + + const qint64 bytesWritten = write(buffer, bytesRead); + if (bytesWritten != bytesRead) + m_source->seek(m_source->pos()-(bytesRead-bytesWritten)); + + } else { + // We're done + close(); + if (bytesRead != 0) + setError(QAudio::IOError); + setState(QAudio::StoppedState); + } + + if (m_state != QAudio::ActiveState) + return; + + if (m_notifyInterval > 0 && (m_intervalTimeStamp.elapsed() + m_intervalOffset) > m_notifyInterval) { + emit notify(); + m_intervalOffset = m_intervalTimeStamp.elapsed() + m_intervalOffset - m_notifyInterval; + m_intervalTimeStamp.restart(); + } +} + +bool QnxAudioOutput::open() +{ + if (!m_format.isValid() || m_format.sampleRate() <= 0) { + if (!m_format.isValid()) + qWarning("QnxAudioOutput: open error, invalid format."); + else + qWarning("QnxAudioOutput: open error, invalid sample rate (%d).", m_format.sampleRate()); + + return false; + } + + int errorCode = 0; + + int card = 0; + int device = 0; + if ((errorCode = snd_pcm_open_preferred(&m_pcmHandle, &card, &device, SND_PCM_OPEN_PLAYBACK)) < 0) { + qWarning("QnxAudioOutput: open error, couldn't open card (0x%x)", -errorCode); + return false; + } + + if ((errorCode = snd_pcm_nonblock_mode(m_pcmHandle, 0)) < 0) { + qWarning("QnxAudioOutput: open error, couldn't set non block mode (0x%x)", -errorCode); + close(); + return false; + } + + addPcmEventFilter(); + + // Necessary so that bytesFree() which uses the "free" member of the status struct works + snd_pcm_plugin_set_disable(m_pcmHandle, PLUGIN_MMAP); + + snd_pcm_channel_info_t info; + memset(&info, 0, sizeof(info)); + info.channel = SND_PCM_CHANNEL_PLAYBACK; + if ((errorCode = snd_pcm_plugin_info(m_pcmHandle, &info)) < 0) { + qWarning("QnxAudioOutput: open error, couldn't get channel info (0x%x)", -errorCode); + close(); + return false; + } + + snd_pcm_channel_params_t params = QnxAudioUtils::formatToChannelParams(m_format, QAudio::AudioOutput, info.max_fragment_size); + setTypeName(¶ms); + + if ((errorCode = snd_pcm_plugin_params(m_pcmHandle, ¶ms)) < 0) { + qWarning("QnxAudioOutput: open error, couldn't set channel params (0x%x)", -errorCode); + close(); + return false; + } + + if ((errorCode = snd_pcm_plugin_prepare(m_pcmHandle, SND_PCM_CHANNEL_PLAYBACK)) < 0) { + qWarning("QnxAudioOutput: open error, couldn't prepare channel (0x%x)", -errorCode); + close(); + return false; + } + + snd_pcm_channel_setup_t setup; + memset(&setup, 0, sizeof(setup)); + setup.channel = SND_PCM_CHANNEL_PLAYBACK; + if ((errorCode = snd_pcm_plugin_setup(m_pcmHandle, &setup)) < 0) { + qWarning("QnxAudioOutput: open error, couldn't get channel setup (0x%x)", -errorCode); + close(); + return false; + } + + m_periodSize = qMin(2048, setup.buf.block.frag_size); + m_startTimeStamp.restart(); + m_intervalTimeStamp.restart(); + m_intervalOffset = 0; + m_bytesWritten = 0; + + createPcmNotifiers(); + + return true; +} + +void QnxAudioOutput::close() +{ + m_timer.stop(); + + destroyPcmNotifiers(); + + if (m_pcmHandle) { +#if SND_PCM_VERSION < SND_PROTOCOL_VERSION('P',3,0,2) + snd_pcm_plugin_flush(m_pcmHandle, SND_PCM_CHANNEL_PLAYBACK); +#else + snd_pcm_plugin_drop(m_pcmHandle, SND_PCM_CHANNEL_PLAYBACK); +#endif + snd_pcm_close(m_pcmHandle); + m_pcmHandle = 0; + } + + if (m_pushSource) { + delete m_source; + m_source = 0; + } +} + +void QnxAudioOutput::setError(QAudio::Error error) +{ + if (m_error != error) { + m_error = error; + emit errorChanged(error); + } +} + +void QnxAudioOutput::setState(QAudio::State state) +{ + if (m_state != state) { + m_state = state; + emit stateChanged(state); + } +} + +qint64 QnxAudioOutput::write(const char *data, qint64 len) +{ + if (!m_pcmHandle) + return 0; + + // Make sure we're writing (N * frame) worth of bytes + const int size = m_format.bytesForFrames(qBound(qint64(0), qint64(bytesFree()), len) / m_format.bytesPerFrame()); + + if (size == 0) + return 0; + + int written = 0; + + if (m_volume < 1.0f) { + char out[size]; + QAudioHelperInternal::qMultiplySamples(m_volume, m_format, data, out, size); + written = snd_pcm_plugin_write(m_pcmHandle, out, size); + } else { + written = snd_pcm_plugin_write(m_pcmHandle, data, size); + } + + if (written > 0) { + m_bytesWritten += written; + setError(QAudio::NoError); + setState(QAudio::ActiveState); + return written; + } else { + close(); + setError(QAudio::FatalError); + setState(QAudio::StoppedState); + return 0; + } +} + +void QnxAudioOutput::suspendInternal(QAudio::State suspendState) +{ + m_timer.stop(); + setState(suspendState); +} + +void QnxAudioOutput::resumeInternal() +{ + if (m_pushSource) { + setState(QAudio::IdleState); + } else { + setState(QAudio::ActiveState); + m_timer.start(); + } +} + +#if _NTO_VERSION >= 700 + +QAudio::State suspendState(const snd_pcm_event_t &event) +{ + Q_ASSERT(event.type == SND_PCM_EVENT_AUDIOMGMT_STATUS); + Q_ASSERT(event.data.audiomgmt_status.new_status == SND_PCM_STATUS_SUSPENDED); + return event.data.audiomgmt_status.flags & SND_PCM_STATUS_EVENT_HARD_SUSPEND + ? QAudio::InterruptedState : QAudio::SuspendedState; +} + +void QnxAudioOutput::addPcmEventFilter() +{ + /* Enable PCM events */ + snd_pcm_filter_t filter; + memset(&filter, 0, sizeof(filter)); + filter.enable = (1<<SND_PCM_EVENT_AUDIOMGMT_STATUS) | + (1<<SND_PCM_EVENT_AUDIOMGMT_MUTE) | + (1<<SND_PCM_EVENT_OUTPUTCLASS); + snd_pcm_set_filter(m_pcmHandle, SND_PCM_CHANNEL_PLAYBACK, &filter); +} + +void QnxAudioOutput::createPcmNotifiers() +{ + // QSocketNotifier::Read for poll based event dispatcher. Exception for + // select based event dispatcher. + m_pcmNotifier = new QSocketNotifier(snd_pcm_file_descriptor(m_pcmHandle, + SND_PCM_CHANNEL_PLAYBACK), + QSocketNotifier::Read, this); + connect(m_pcmNotifier, &QSocketNotifier::activated, + this, &QnxAudioOutput::pcmNotifierActivated); +} + +void QnxAudioOutput::destroyPcmNotifiers() +{ + if (m_pcmNotifier) { + delete m_pcmNotifier; + m_pcmNotifier = 0; + } +} + +void QnxAudioOutput::setTypeName(snd_pcm_channel_params_t *params) +{ + if (m_category.isEmpty()) + return; + + QByteArray latin1Category = m_category.toLatin1(); + + if (QString::fromLatin1(latin1Category) != m_category) { + qWarning("QnxAudioOutput: audio category name isn't a Latin1 string."); + return; + } + + if (latin1Category.size() >= static_cast<int>(sizeof(params->audio_type_name))) { + qWarning("QnxAudioOutput: audio category name too long."); + return; + } + + strcpy(params->audio_type_name, latin1Category.constData()); +} + +void QnxAudioOutput::pcmNotifierActivated(int socket) +{ + Q_UNUSED(socket); + + snd_pcm_event_t pcm_event; + memset(&pcm_event, 0, sizeof(pcm_event)); + while (snd_pcm_channel_read_event(m_pcmHandle, SND_PCM_CHANNEL_PLAYBACK, &pcm_event) == 0) { + if (pcm_event.type == SND_PCM_EVENT_AUDIOMGMT_STATUS) { + if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_SUSPENDED) + suspendInternal(suspendState(pcm_event)); + else if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_RUNNING) + resumeInternal(); + else if (pcm_event.data.audiomgmt_status.new_status == SND_PCM_STATUS_PAUSED) + suspendInternal(QAudio::SuspendedState); + } + } +} + +#else + +void QnxAudioOutput::addPcmEventFilter() {} +void QnxAudioOutput::createPcmNotifiers() {} +void QnxAudioOutput::destroyPcmNotifiers() {} +void QnxAudioOutput::setTypeName(snd_pcm_channel_params_t *) {} + +#endif + +QnxPushIODevice::QnxPushIODevice(QnxAudioOutput *output) + : QIODevice(output), + m_output(output) +{ +} + +QnxPushIODevice::~QnxPushIODevice() +{ +} + +qint64 QnxPushIODevice::readData(char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + return 0; +} + +qint64 QnxPushIODevice::writeData(const char *data, qint64 len) +{ + int retry = 0; + qint64 written = 0; + + if (m_output->state() == QAudio::ActiveState + || m_output->state() == QAudio::IdleState) { + while (written < len) { + const int writeSize = m_output->write(data + written, len - written); + + if (writeSize <= 0) { + retry++; + if (retry > 10) + return written; + else + continue; + } + + retry = 0; + written += writeSize; + } + } + + return written; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/audio/qnx/qnxaudiooutput_p.h b/src/multimedia/audio/qnx/qnxaudiooutput_p.h new file mode 100644 index 000000000..4fff7ad38 --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudiooutput_p.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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$ +** +****************************************************************************/ + +#ifndef QNXAUDIOOUTPUT_H +#define QNXAUDIOOUTPUT_H + +#include "qaudiosystem_p.h" + +#include <QElapsedTimer> +#include <QTimer> +#include <QIODevice> +#include <QSocketNotifier> + +#include <sys/asoundlib.h> +#include <sys/neutrino.h> + +QT_BEGIN_NAMESPACE + +class QnxPushIODevice; + +class QnxAudioOutput : public QAbstractAudioOutput +{ + Q_OBJECT + +public: + QnxAudioOutput(); + ~QnxAudioOutput(); + + void start(QIODevice *source) override; + QIODevice *start() override; + void stop() override; + void reset() override; + void suspend() override; + void resume() override; + int bytesFree() const override; + int periodSize() const override; + void setBufferSize(int) override {} + int bufferSize() const override { return 0; } + void setNotifyInterval(int ms) override; + int notifyInterval() const override; + qint64 processedUSecs() const override; + qint64 elapsedUSecs() const override; + QAudio::Error error() const override; + QAudio::State state() const override; + void setFormat(const QAudioFormat &format) override; + QAudioFormat format() const override; + void setVolume(qreal volume) override; + qreal volume() const override; + void setCategory(const QString &category) override; + QString category() const override; + +private slots: + void pullData(); + +private: + bool open(); + void close(); + void setError(QAudio::Error error); + void setState(QAudio::State state); + + void addPcmEventFilter(); + void createPcmNotifiers(); + void destroyPcmNotifiers(); + void setTypeName(snd_pcm_channel_params_t *params); + + void suspendInternal(QAudio::State suspendState); + void resumeInternal(); + + friend class QnxPushIODevice; + qint64 write(const char *data, qint64 len); + + QIODevice *m_source; + bool m_pushSource; + QTimer m_timer; + + int m_notifyInterval; + QAudio::Error m_error; + QAudio::State m_state; + QAudioFormat m_format; + qreal m_volume; + QString m_category; + int m_periodSize; + + snd_pcm_t *m_pcmHandle; + qint64 m_bytesWritten; + QElapsedTimer m_startTimeStamp; + QElapsedTimer m_intervalTimeStamp; + qint64 m_intervalOffset; + +#if _NTO_VERSION >= 700 + QSocketNotifier *m_pcmNotifier; + +private slots: + void pcmNotifierActivated(int socket); +#endif +}; + +class QnxPushIODevice : public QIODevice +{ + Q_OBJECT +public: + explicit QnxPushIODevice(QnxAudioOutput *output); + ~QnxPushIODevice(); + + qint64 readData(char *data, qint64 len); + qint64 writeData(const char *data, qint64 len); + +private: + QnxAudioOutput *m_output; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/audio/qnx/qnxaudioutils.cpp b/src/multimedia/audio/qnx/qnxaudioutils.cpp new file mode 100644 index 000000000..aef4492d8 --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudioutils.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "qnxaudioutils_p.h" + +QT_BEGIN_NAMESPACE + +snd_pcm_channel_params_t QnxAudioUtils::formatToChannelParams(const QAudioFormat &format, QAudio::Mode mode, int fragmentSize) +{ + snd_pcm_channel_params_t params; + memset(¶ms, 0, sizeof(params)); + params.channel = (mode == QAudio::AudioOutput) ? SND_PCM_CHANNEL_PLAYBACK : SND_PCM_CHANNEL_CAPTURE; + params.mode = SND_PCM_MODE_BLOCK; + params.start_mode = SND_PCM_START_DATA; + params.stop_mode = SND_PCM_STOP_ROLLOVER; + params.buf.block.frag_size = fragmentSize; + params.buf.block.frags_min = 1; + params.buf.block.frags_max = 1; + strcpy(params.sw_mixer_subchn_name, "QAudio Channel"); + + params.format.interleave = 1; + params.format.rate = format.sampleRate(); + params.format.voices = format.channelCount(); + + switch (format.sampleSize()) { + case 8: + switch (format.sampleType()) { + case QAudioFormat::SignedInt: + params.format.format = SND_PCM_SFMT_S8; + break; + case QAudioFormat::UnSignedInt: + params.format.format = SND_PCM_SFMT_U8; + break; + default: + break; + } + break; + + case 16: + switch (format.sampleType()) { + case QAudioFormat::SignedInt: + if (format.byteOrder() == QAudioFormat::LittleEndian) { + params.format.format = SND_PCM_SFMT_S16_LE; + } else { + params.format.format = SND_PCM_SFMT_S16_BE; + } + break; + case QAudioFormat::UnSignedInt: + if (format.byteOrder() == QAudioFormat::LittleEndian) { + params.format.format = SND_PCM_SFMT_U16_LE; + } else { + params.format.format = SND_PCM_SFMT_U16_BE; + } + break; + default: + break; + } + break; + + case 32: + switch (format.sampleType()) { + case QAudioFormat::SignedInt: + if (format.byteOrder() == QAudioFormat::LittleEndian) { + params.format.format = SND_PCM_SFMT_S32_LE; + } else { + params.format.format = SND_PCM_SFMT_S32_BE; + } + break; + case QAudioFormat::UnSignedInt: + if (format.byteOrder() == QAudioFormat::LittleEndian) { + params.format.format = SND_PCM_SFMT_U32_LE; + } else { + params.format.format = SND_PCM_SFMT_U32_BE; + } + break; + case QAudioFormat::Float: + if (format.byteOrder() == QAudioFormat::LittleEndian) { + params.format.format = SND_PCM_SFMT_FLOAT_LE; + } else { + params.format.format = SND_PCM_SFMT_FLOAT_BE; + } + break; + default: + break; + } + break; + } + + return params; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/audio/qnx/qnxaudioutils_p.h b/src/multimedia/audio/qnx/qnxaudioutils_p.h new file mode 100644 index 000000000..b997e0c45 --- /dev/null +++ b/src/multimedia/audio/qnx/qnxaudioutils_p.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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$ +** +****************************************************************************/ + +#ifndef QNXAUDIOUTILS_H +#define QNXAUDIOUTILS_H + +#include "qaudiosystem_p.h" +#include <sys/asoundlib.h> + +QT_BEGIN_NAMESPACE + +namespace QnxAudioUtils +{ + snd_pcm_channel_params_t formatToChannelParams(const QAudioFormat &format, QAudio::Mode mode, int fragmentSize); +} + +QT_END_NAMESPACE + +#endif |