Move win32 and Alsa audio backends into plugins.
Change-Id: I9835cf5ee97900569f26421a19543b485e933051 Reviewed-by: Yoann Lopes <yoann.lopes@digia.com>
This commit is contained in:
committed by
The Qt Project
parent
0ab81ef59f
commit
2d54da2d39
3
src/plugins/alsa/alsa.json
Normal file
3
src/plugins/alsa/alsa.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Keys": ["alsa"]
|
||||
}
|
||||
23
src/plugins/alsa/alsa.pro
Normal file
23
src/plugins/alsa/alsa.pro
Normal file
@@ -0,0 +1,23 @@
|
||||
TARGET = qtaudio_alsa
|
||||
QT += multimedia-private
|
||||
|
||||
PLUGIN_TYPE = audio
|
||||
PLUGIN_CLASS_NAME = QAlsaPlugin
|
||||
load(qt_plugin)
|
||||
|
||||
LIBS += -lasound
|
||||
|
||||
HEADERS += \
|
||||
qalsaplugin.h \
|
||||
qalsaaudiodeviceinfo.h \
|
||||
qalsaaudioinput.h \
|
||||
qalsaaudiooutput.h
|
||||
|
||||
SOURCES += \
|
||||
qalsaplugin.cpp \
|
||||
qalsaaudiodeviceinfo.cpp \
|
||||
qalsaaudioinput.cpp \
|
||||
qalsaaudiooutput.cpp
|
||||
|
||||
OTHER_FILES += \
|
||||
alsa.json
|
||||
464
src/plugins/alsa/qalsaaudiodeviceinfo.cpp
Normal file
464
src/plugins/alsa/qalsaaudiodeviceinfo.cpp
Normal file
@@ -0,0 +1,464 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** 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 Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience
|
||||
// of other Qt classes. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// INTERNAL USE ONLY: Do NOT use for any other purpose.
|
||||
//
|
||||
|
||||
#include "qalsaaudiodeviceinfo.h"
|
||||
|
||||
#include <alsa/version.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QAlsaAudioDeviceInfo::QAlsaAudioDeviceInfo(QByteArray dev, QAudio::Mode mode)
|
||||
{
|
||||
handle = 0;
|
||||
|
||||
device = QLatin1String(dev);
|
||||
this->mode = mode;
|
||||
|
||||
checkSurround();
|
||||
}
|
||||
|
||||
QAlsaAudioDeviceInfo::~QAlsaAudioDeviceInfo()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool QAlsaAudioDeviceInfo::isFormatSupported(const QAudioFormat& format) const
|
||||
{
|
||||
return testSettings(format);
|
||||
}
|
||||
|
||||
QAudioFormat QAlsaAudioDeviceInfo::preferredFormat() const
|
||||
{
|
||||
QAudioFormat nearest;
|
||||
if(mode == QAudio::AudioOutput) {
|
||||
nearest.setSampleRate(44100);
|
||||
nearest.setChannelCount(2);
|
||||
nearest.setByteOrder(QAudioFormat::LittleEndian);
|
||||
nearest.setSampleType(QAudioFormat::SignedInt);
|
||||
nearest.setSampleSize(16);
|
||||
nearest.setCodec(QLatin1String("audio/pcm"));
|
||||
} else {
|
||||
nearest.setSampleRate(8000);
|
||||
nearest.setChannelCount(1);
|
||||
nearest.setSampleType(QAudioFormat::UnSignedInt);
|
||||
nearest.setSampleSize(8);
|
||||
nearest.setCodec(QLatin1String("audio/pcm"));
|
||||
if(!testSettings(nearest)) {
|
||||
nearest.setChannelCount(2);
|
||||
nearest.setSampleSize(16);
|
||||
nearest.setSampleType(QAudioFormat::SignedInt);
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
QString QAlsaAudioDeviceInfo::deviceName() const
|
||||
{
|
||||
return device;
|
||||
}
|
||||
|
||||
QStringList QAlsaAudioDeviceInfo::supportedCodecs()
|
||||
{
|
||||
updateLists();
|
||||
return codecz;
|
||||
}
|
||||
|
||||
QList<int> QAlsaAudioDeviceInfo::supportedSampleRates()
|
||||
{
|
||||
updateLists();
|
||||
return sampleRatez;
|
||||
}
|
||||
|
||||
QList<int> QAlsaAudioDeviceInfo::supportedChannelCounts()
|
||||
{
|
||||
updateLists();
|
||||
return channelz;
|
||||
}
|
||||
|
||||
QList<int> QAlsaAudioDeviceInfo::supportedSampleSizes()
|
||||
{
|
||||
updateLists();
|
||||
return sizez;
|
||||
}
|
||||
|
||||
QList<QAudioFormat::Endian> QAlsaAudioDeviceInfo::supportedByteOrders()
|
||||
{
|
||||
updateLists();
|
||||
return byteOrderz;
|
||||
}
|
||||
|
||||
QList<QAudioFormat::SampleType> QAlsaAudioDeviceInfo::supportedSampleTypes()
|
||||
{
|
||||
updateLists();
|
||||
return typez;
|
||||
}
|
||||
|
||||
bool QAlsaAudioDeviceInfo::open()
|
||||
{
|
||||
int err = 0;
|
||||
QString dev = device;
|
||||
QList<QByteArray> devices = availableDevices(mode);
|
||||
|
||||
if(dev.compare(QLatin1String("default")) == 0) {
|
||||
#if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14)
|
||||
if (devices.size() > 0)
|
||||
dev = QLatin1String(devices.first().constData());
|
||||
else
|
||||
return false;
|
||||
#else
|
||||
dev = QLatin1String("hw:0,0");
|
||||
#endif
|
||||
} else {
|
||||
#if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14)
|
||||
dev = device;
|
||||
#else
|
||||
int idx = 0;
|
||||
char *name;
|
||||
|
||||
QString shortName = device.mid(device.indexOf(QLatin1String("="),0)+1);
|
||||
|
||||
while (snd_card_get_name(idx,&name) == 0) {
|
||||
if(dev.contains(QLatin1String(name)))
|
||||
break;
|
||||
idx++;
|
||||
}
|
||||
dev = QString(QLatin1String("hw:%1,0")).arg(idx);
|
||||
#endif
|
||||
}
|
||||
if(mode == QAudio::AudioOutput) {
|
||||
err=snd_pcm_open( &handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_PLAYBACK,0);
|
||||
} else {
|
||||
err=snd_pcm_open( &handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_CAPTURE,0);
|
||||
}
|
||||
if(err < 0) {
|
||||
handle = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QAlsaAudioDeviceInfo::close()
|
||||
{
|
||||
if(handle)
|
||||
snd_pcm_close(handle);
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
bool QAlsaAudioDeviceInfo::testSettings(const QAudioFormat& format) const
|
||||
{
|
||||
// Set nearest to closest settings that do work.
|
||||
// See if what is in settings will work (return value).
|
||||
int err = -1;
|
||||
snd_pcm_t* pcmHandle;
|
||||
snd_pcm_hw_params_t *params;
|
||||
QString dev;
|
||||
|
||||
#if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14)
|
||||
dev = device;
|
||||
if (dev.compare(QLatin1String("default")) == 0) {
|
||||
QList<QByteArray> devices = availableDevices(QAudio::AudioOutput);
|
||||
if (!devices.isEmpty())
|
||||
dev = QLatin1String(devices.first().constData());
|
||||
}
|
||||
#else
|
||||
if (dev.compare(QLatin1String("default")) == 0) {
|
||||
dev = QLatin1String("hw:0,0");
|
||||
} else {
|
||||
int idx = 0;
|
||||
char *name;
|
||||
|
||||
QString shortName = device.mid(device.indexOf(QLatin1String("="),0)+1);
|
||||
|
||||
while(snd_card_get_name(idx,&name) == 0) {
|
||||
if(shortName.compare(QLatin1String(name)) == 0)
|
||||
break;
|
||||
idx++;
|
||||
}
|
||||
dev = QString(QLatin1String("hw:%1,0")).arg(idx);
|
||||
}
|
||||
#endif
|
||||
|
||||
snd_pcm_stream_t stream = mode == QAudio::AudioOutput
|
||||
? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE;
|
||||
|
||||
if (snd_pcm_open(&pcmHandle, dev.toLocal8Bit().constData(), stream, 0) < 0)
|
||||
return false;
|
||||
|
||||
snd_pcm_nonblock(pcmHandle, 0);
|
||||
snd_pcm_hw_params_alloca(¶ms);
|
||||
snd_pcm_hw_params_any(pcmHandle, params);
|
||||
|
||||
// set the values!
|
||||
snd_pcm_hw_params_set_channels(pcmHandle, params, format.channelCount());
|
||||
snd_pcm_hw_params_set_rate(pcmHandle, params, format.sampleRate(), 0);
|
||||
|
||||
snd_pcm_format_t pcmFormat = SND_PCM_FORMAT_UNKNOWN;
|
||||
switch (format.sampleSize()) {
|
||||
case 8:
|
||||
if (format.sampleType() == QAudioFormat::SignedInt)
|
||||
pcmFormat = SND_PCM_FORMAT_S8;
|
||||
else if (format.sampleType() == QAudioFormat::UnSignedInt)
|
||||
pcmFormat = SND_PCM_FORMAT_U8;
|
||||
break;
|
||||
case 16:
|
||||
if (format.sampleType() == QAudioFormat::SignedInt) {
|
||||
pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
|
||||
? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_S16_BE;
|
||||
} else if (format.sampleType() == QAudioFormat::UnSignedInt) {
|
||||
pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
|
||||
? SND_PCM_FORMAT_U16_LE : SND_PCM_FORMAT_U16_BE;
|
||||
}
|
||||
break;
|
||||
case 32:
|
||||
if (format.sampleType() == QAudioFormat::SignedInt) {
|
||||
pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
|
||||
? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_S32_BE;
|
||||
} else if (format.sampleType() == QAudioFormat::UnSignedInt) {
|
||||
pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
|
||||
? SND_PCM_FORMAT_U32_LE : SND_PCM_FORMAT_U32_BE;
|
||||
} else if (format.sampleType() == QAudioFormat::Float) {
|
||||
pcmFormat = format.byteOrder() == QAudioFormat::LittleEndian
|
||||
? SND_PCM_FORMAT_FLOAT_LE : SND_PCM_FORMAT_FLOAT_BE;
|
||||
}
|
||||
}
|
||||
|
||||
if (pcmFormat != SND_PCM_FORMAT_UNKNOWN)
|
||||
err = snd_pcm_hw_params_set_format(pcmHandle, params, pcmFormat);
|
||||
|
||||
// For now, just accept only audio/pcm codec
|
||||
if (!format.codec().startsWith(QLatin1String("audio/pcm")))
|
||||
err = -1;
|
||||
|
||||
if (err >= 0 && format.channelCount() != -1) {
|
||||
err = snd_pcm_hw_params_test_channels(pcmHandle, params, format.channelCount());
|
||||
if (err >= 0)
|
||||
err = snd_pcm_hw_params_set_channels(pcmHandle, params, format.channelCount());
|
||||
}
|
||||
|
||||
if (err >= 0 && format.sampleRate() != -1) {
|
||||
err = snd_pcm_hw_params_test_rate(pcmHandle, params, format.sampleRate(), 0);
|
||||
if (err >= 0)
|
||||
err = snd_pcm_hw_params_set_rate(pcmHandle, params, format.sampleRate(), 0);
|
||||
}
|
||||
|
||||
if (err >= 0 && pcmFormat != SND_PCM_FORMAT_UNKNOWN)
|
||||
err = snd_pcm_hw_params_set_format(pcmHandle, params, pcmFormat);
|
||||
|
||||
if (err >= 0)
|
||||
err = snd_pcm_hw_params(pcmHandle, params);
|
||||
|
||||
snd_pcm_close(pcmHandle);
|
||||
|
||||
return (err == 0);
|
||||
}
|
||||
|
||||
void QAlsaAudioDeviceInfo::updateLists()
|
||||
{
|
||||
// redo all lists based on current settings
|
||||
sampleRatez.clear();
|
||||
channelz.clear();
|
||||
sizez.clear();
|
||||
byteOrderz.clear();
|
||||
typez.clear();
|
||||
codecz.clear();
|
||||
|
||||
if(!handle)
|
||||
open();
|
||||
|
||||
if(!handle)
|
||||
return;
|
||||
|
||||
for(int i=0; i<(int)MAX_SAMPLE_RATES; i++) {
|
||||
//if(snd_pcm_hw_params_test_rate(handle, params, SAMPLE_RATES[i], dir) == 0)
|
||||
sampleRatez.append(SAMPLE_RATES[i]);
|
||||
}
|
||||
channelz.append(1);
|
||||
channelz.append(2);
|
||||
if (surround40) channelz.append(4);
|
||||
if (surround51) channelz.append(6);
|
||||
if (surround71) channelz.append(8);
|
||||
sizez.append(8);
|
||||
sizez.append(16);
|
||||
sizez.append(32);
|
||||
byteOrderz.append(QAudioFormat::LittleEndian);
|
||||
byteOrderz.append(QAudioFormat::BigEndian);
|
||||
typez.append(QAudioFormat::SignedInt);
|
||||
typez.append(QAudioFormat::UnSignedInt);
|
||||
typez.append(QAudioFormat::Float);
|
||||
codecz.append(QLatin1String("audio/pcm"));
|
||||
close();
|
||||
}
|
||||
|
||||
QList<QByteArray> QAlsaAudioDeviceInfo::availableDevices(QAudio::Mode mode)
|
||||
{
|
||||
QList<QByteArray> devices;
|
||||
QByteArray filter;
|
||||
|
||||
#if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14)
|
||||
// Create a list of all current audio devices that support mode
|
||||
void **hints;
|
||||
char *name, *descr, *io;
|
||||
int card = -1;
|
||||
|
||||
if(mode == QAudio::AudioInput) {
|
||||
filter = "Input";
|
||||
} else {
|
||||
filter = "Output";
|
||||
}
|
||||
|
||||
while (snd_card_next(&card) == 0 && card >= 0) {
|
||||
if (snd_device_name_hint(card, "pcm", &hints) < 0)
|
||||
continue;
|
||||
|
||||
void **n = hints;
|
||||
while (*n != NULL) {
|
||||
name = snd_device_name_get_hint(*n, "NAME");
|
||||
if (name != 0 && qstrcmp(name, "null") != 0) {
|
||||
descr = snd_device_name_get_hint(*n, "DESC");
|
||||
io = snd_device_name_get_hint(*n, "IOID");
|
||||
|
||||
if ((descr != NULL) && ((io == NULL) || (io == filter))) {
|
||||
QString deviceName = QLatin1String(name);
|
||||
QString deviceDescription = QLatin1String(descr);
|
||||
if (deviceDescription.contains(QLatin1String("Default Audio Device")))
|
||||
devices.prepend(deviceName.toLocal8Bit().constData());
|
||||
else
|
||||
devices.append(deviceName.toLocal8Bit().constData());
|
||||
}
|
||||
|
||||
free(descr);
|
||||
free(io);
|
||||
}
|
||||
free(name);
|
||||
++n;
|
||||
}
|
||||
|
||||
snd_device_name_free_hint(hints);
|
||||
}
|
||||
#else
|
||||
int idx = 0;
|
||||
char* name;
|
||||
|
||||
while(snd_card_get_name(idx,&name) == 0) {
|
||||
devices.append(name);
|
||||
idx++;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (devices.size() > 0)
|
||||
devices.append("default");
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
QByteArray QAlsaAudioDeviceInfo::defaultInputDevice()
|
||||
{
|
||||
QList<QByteArray> devices = availableDevices(QAudio::AudioInput);
|
||||
if(devices.size() == 0)
|
||||
return QByteArray();
|
||||
|
||||
return devices.first();
|
||||
}
|
||||
|
||||
QByteArray QAlsaAudioDeviceInfo::defaultOutputDevice()
|
||||
{
|
||||
QList<QByteArray> devices = availableDevices(QAudio::AudioOutput);
|
||||
if(devices.size() == 0)
|
||||
return QByteArray();
|
||||
|
||||
return devices.first();
|
||||
}
|
||||
|
||||
void QAlsaAudioDeviceInfo::checkSurround()
|
||||
{
|
||||
surround40 = false;
|
||||
surround51 = false;
|
||||
surround71 = false;
|
||||
|
||||
void **hints;
|
||||
char *name, *descr, *io;
|
||||
int card = -1;
|
||||
|
||||
while (snd_card_next(&card) == 0 && card >= 0) {
|
||||
if (snd_device_name_hint(card, "pcm", &hints) < 0)
|
||||
continue;
|
||||
|
||||
void **n = hints;
|
||||
while (*n != NULL) {
|
||||
name = snd_device_name_get_hint(*n, "NAME");
|
||||
descr = snd_device_name_get_hint(*n, "DESC");
|
||||
io = snd_device_name_get_hint(*n, "IOID");
|
||||
if((name != NULL) && (descr != NULL)) {
|
||||
QString deviceName = QLatin1String(name);
|
||||
if (mode == QAudio::AudioOutput) {
|
||||
if(deviceName.contains(QLatin1String("surround40")))
|
||||
surround40 = true;
|
||||
if(deviceName.contains(QLatin1String("surround51")))
|
||||
surround51 = true;
|
||||
if(deviceName.contains(QLatin1String("surround71")))
|
||||
surround71 = true;
|
||||
}
|
||||
}
|
||||
if(name != NULL)
|
||||
free(name);
|
||||
if(descr != NULL)
|
||||
free(descr);
|
||||
if(io != NULL)
|
||||
free(io);
|
||||
++n;
|
||||
}
|
||||
|
||||
snd_device_name_free_hint(hints);
|
||||
}
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
122
src/plugins/alsa/qalsaaudiodeviceinfo.h
Normal file
122
src/plugins/alsa/qalsaaudiodeviceinfo.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** 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 Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience
|
||||
// of other Qt classes. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
|
||||
#ifndef QALSAAUDIODEVICEINFO_H
|
||||
#define QALSAAUDIODEVICEINFO_H
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtCore/qstringlist.h>
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
|
||||
#include <QtMultimedia/qaudio.h>
|
||||
#include <QtMultimedia/qaudiodeviceinfo.h>
|
||||
#include <QtMultimedia/qaudiosystem.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
||||
const unsigned int MAX_SAMPLE_RATES = 5;
|
||||
const unsigned int SAMPLE_RATES[] =
|
||||
{ 8000, 11025, 22050, 44100, 48000 };
|
||||
|
||||
class QAlsaAudioDeviceInfo : public QAbstractAudioDeviceInfo
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QAlsaAudioDeviceInfo(QByteArray dev,QAudio::Mode mode);
|
||||
~QAlsaAudioDeviceInfo();
|
||||
|
||||
bool testSettings(const QAudioFormat& format) const;
|
||||
void updateLists();
|
||||
QAudioFormat preferredFormat() const;
|
||||
bool isFormatSupported(const QAudioFormat& format) const;
|
||||
QString deviceName() const;
|
||||
QStringList supportedCodecs();
|
||||
QList<int> supportedSampleRates();
|
||||
QList<int> supportedChannelCounts();
|
||||
QList<int> supportedSampleSizes();
|
||||
QList<QAudioFormat::Endian> supportedByteOrders();
|
||||
QList<QAudioFormat::SampleType> supportedSampleTypes();
|
||||
static QByteArray defaultInputDevice();
|
||||
static QByteArray defaultOutputDevice();
|
||||
static QList<QByteArray> availableDevices(QAudio::Mode);
|
||||
|
||||
private:
|
||||
bool open();
|
||||
void close();
|
||||
|
||||
void checkSurround();
|
||||
bool surround40;
|
||||
bool surround51;
|
||||
bool surround71;
|
||||
|
||||
QString device;
|
||||
QAudio::Mode mode;
|
||||
QAudioFormat nearest;
|
||||
QList<int> sampleRatez;
|
||||
QList<int> channelz;
|
||||
QList<int> sizez;
|
||||
QList<QAudioFormat::Endian> byteOrderz;
|
||||
QStringList codecz;
|
||||
QList<QAudioFormat::SampleType> typez;
|
||||
snd_pcm_t* handle;
|
||||
snd_pcm_hw_params_t *params;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
||||
#endif // QALSAAUDIODEVICEINFO_H
|
||||
882
src/plugins/alsa/qalsaaudioinput.cpp
Normal file
882
src/plugins/alsa/qalsaaudioinput.cpp
Normal file
@@ -0,0 +1,882 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** 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 Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience
|
||||
// of other Qt classes. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// INTERNAL USE ONLY: Do NOT use for any other purpose.
|
||||
//
|
||||
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QtMultimedia/private/qaudiohelpers_p.h>
|
||||
#include "qalsaaudioinput.h"
|
||||
#include "qalsaaudiodeviceinfo.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
//#define DEBUG_AUDIO 1
|
||||
|
||||
QAlsaAudioInput::QAlsaAudioInput(const QByteArray &device)
|
||||
{
|
||||
bytesAvailable = 0;
|
||||
handle = 0;
|
||||
ahandler = 0;
|
||||
access = SND_PCM_ACCESS_RW_INTERLEAVED;
|
||||
pcmformat = SND_PCM_FORMAT_S16;
|
||||
buffer_size = 0;
|
||||
period_size = 0;
|
||||
buffer_time = 100000;
|
||||
period_time = 20000;
|
||||
totalTimeValue = 0;
|
||||
intervalTime = 1000;
|
||||
errorState = QAudio::NoError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
audioSource = 0;
|
||||
pullMode = true;
|
||||
resuming = false;
|
||||
|
||||
m_volume = 1.0f;
|
||||
|
||||
m_device = device;
|
||||
|
||||
timer = new QTimer(this);
|
||||
connect(timer,SIGNAL(timeout()),SLOT(userFeed()));
|
||||
}
|
||||
|
||||
QAlsaAudioInput::~QAlsaAudioInput()
|
||||
{
|
||||
close();
|
||||
disconnect(timer, SIGNAL(timeout()));
|
||||
QCoreApplication::processEvents();
|
||||
delete timer;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::setVolume(qreal vol)
|
||||
{
|
||||
m_volume = vol;
|
||||
}
|
||||
|
||||
qreal QAlsaAudioInput::volume() const
|
||||
{
|
||||
return m_volume;
|
||||
}
|
||||
|
||||
QAudio::Error QAlsaAudioInput::error() const
|
||||
{
|
||||
return errorState;
|
||||
}
|
||||
|
||||
QAudio::State QAlsaAudioInput::state() const
|
||||
{
|
||||
return deviceState;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::setFormat(const QAudioFormat& fmt)
|
||||
{
|
||||
if (deviceState == QAudio::StoppedState)
|
||||
settings = fmt;
|
||||
}
|
||||
|
||||
QAudioFormat QAlsaAudioInput::format() const
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
int QAlsaAudioInput::xrun_recovery(int err)
|
||||
{
|
||||
int count = 0;
|
||||
bool reset = false;
|
||||
|
||||
if(err == -EPIPE) {
|
||||
errorState = QAudio::UnderrunError;
|
||||
err = snd_pcm_prepare(handle);
|
||||
if(err < 0)
|
||||
reset = true;
|
||||
else {
|
||||
bytesAvailable = checkBytesReady();
|
||||
if (bytesAvailable <= 0)
|
||||
reset = true;
|
||||
}
|
||||
|
||||
} else if((err == -ESTRPIPE)||(err == -EIO)) {
|
||||
errorState = QAudio::IOError;
|
||||
while((err = snd_pcm_resume(handle)) == -EAGAIN){
|
||||
usleep(100);
|
||||
count++;
|
||||
if(count > 5) {
|
||||
reset = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(err < 0) {
|
||||
err = snd_pcm_prepare(handle);
|
||||
if(err < 0)
|
||||
reset = true;
|
||||
}
|
||||
}
|
||||
if(reset) {
|
||||
close();
|
||||
open();
|
||||
snd_pcm_prepare(handle);
|
||||
return 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int QAlsaAudioInput::setFormat()
|
||||
{
|
||||
snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
|
||||
|
||||
if(settings.sampleSize() == 8) {
|
||||
format = SND_PCM_FORMAT_U8;
|
||||
} else if(settings.sampleSize() == 16) {
|
||||
if(settings.sampleType() == QAudioFormat::SignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
format = SND_PCM_FORMAT_S16_LE;
|
||||
else
|
||||
format = SND_PCM_FORMAT_S16_BE;
|
||||
} else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
format = SND_PCM_FORMAT_U16_LE;
|
||||
else
|
||||
format = SND_PCM_FORMAT_U16_BE;
|
||||
}
|
||||
} else if(settings.sampleSize() == 24) {
|
||||
if(settings.sampleType() == QAudioFormat::SignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
format = SND_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
format = SND_PCM_FORMAT_S24_BE;
|
||||
} else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
format = SND_PCM_FORMAT_U24_LE;
|
||||
else
|
||||
format = SND_PCM_FORMAT_U24_BE;
|
||||
}
|
||||
} else if(settings.sampleSize() == 32) {
|
||||
if(settings.sampleType() == QAudioFormat::SignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
format = SND_PCM_FORMAT_S32_LE;
|
||||
else
|
||||
format = SND_PCM_FORMAT_S32_BE;
|
||||
} else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
format = SND_PCM_FORMAT_U32_LE;
|
||||
else
|
||||
format = SND_PCM_FORMAT_U32_BE;
|
||||
} else if(settings.sampleType() == QAudioFormat::Float) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
format = SND_PCM_FORMAT_FLOAT_LE;
|
||||
else
|
||||
format = SND_PCM_FORMAT_FLOAT_BE;
|
||||
}
|
||||
} else if(settings.sampleSize() == 64) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
format = SND_PCM_FORMAT_FLOAT64_LE;
|
||||
else
|
||||
format = SND_PCM_FORMAT_FLOAT64_BE;
|
||||
}
|
||||
|
||||
return format != SND_PCM_FORMAT_UNKNOWN
|
||||
? snd_pcm_hw_params_set_format( handle, hwparams, format)
|
||||
: -1;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::start(QIODevice* device)
|
||||
{
|
||||
if(deviceState != QAudio::StoppedState)
|
||||
close();
|
||||
|
||||
if(!pullMode && audioSource)
|
||||
delete audioSource;
|
||||
|
||||
pullMode = true;
|
||||
audioSource = device;
|
||||
|
||||
deviceState = QAudio::ActiveState;
|
||||
|
||||
if( !open() )
|
||||
return;
|
||||
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
|
||||
QIODevice* QAlsaAudioInput::start()
|
||||
{
|
||||
if(deviceState != QAudio::StoppedState)
|
||||
close();
|
||||
|
||||
if(!pullMode && audioSource)
|
||||
delete audioSource;
|
||||
|
||||
pullMode = false;
|
||||
audioSource = new InputPrivate(this);
|
||||
audioSource->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
|
||||
|
||||
deviceState = QAudio::IdleState;
|
||||
|
||||
if( !open() )
|
||||
return 0;
|
||||
|
||||
emit stateChanged(deviceState);
|
||||
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::stop()
|
||||
{
|
||||
if(deviceState == QAudio::StoppedState)
|
||||
return;
|
||||
|
||||
deviceState = QAudio::StoppedState;
|
||||
|
||||
close();
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
|
||||
bool QAlsaAudioInput::open()
|
||||
{
|
||||
#ifdef DEBUG_AUDIO
|
||||
QTime now(QTime::currentTime());
|
||||
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
|
||||
#endif
|
||||
clockStamp.restart();
|
||||
timeStamp.restart();
|
||||
elapsedTimeOffset = 0;
|
||||
|
||||
int dir;
|
||||
int err = 0;
|
||||
int count=0;
|
||||
unsigned int sampleRate=settings.sampleRate();
|
||||
|
||||
if (!settings.isValid()) {
|
||||
qWarning("QAudioInput: open error, invalid format.");
|
||||
} else if (settings.sampleRate() <= 0) {
|
||||
qWarning("QAudioInput: open error, invalid sample rate (%d).",
|
||||
settings.sampleRate());
|
||||
} else {
|
||||
err = -1;
|
||||
}
|
||||
|
||||
if (err == 0) {
|
||||
errorState = QAudio::OpenError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
emit errorChanged(errorState);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
QString dev = QString(QLatin1String(m_device.constData()));
|
||||
QList<QByteArray> devices = QAlsaAudioDeviceInfo::availableDevices(QAudio::AudioInput);
|
||||
if(dev.compare(QLatin1String("default")) == 0) {
|
||||
#if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14)
|
||||
if (devices.size() > 0)
|
||||
dev = QLatin1String(devices.first());
|
||||
else
|
||||
return false;
|
||||
#else
|
||||
dev = QLatin1String("hw:0,0");
|
||||
#endif
|
||||
} else {
|
||||
#if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14)
|
||||
dev = QLatin1String(m_device);
|
||||
#else
|
||||
int idx = 0;
|
||||
char *name;
|
||||
|
||||
QString shortName = QLatin1String(m_device.mid(m_device.indexOf('=',0)+1).constData());
|
||||
|
||||
while(snd_card_get_name(idx,&name) == 0) {
|
||||
if(qstrncmp(shortName.toLocal8Bit().constData(),name,shortName.length()) == 0)
|
||||
break;
|
||||
idx++;
|
||||
}
|
||||
dev = QString(QLatin1String("hw:%1,0")).arg(idx);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Step 1: try and open the device
|
||||
while((count < 5) && (err < 0)) {
|
||||
err=snd_pcm_open(&handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_CAPTURE,0);
|
||||
if(err < 0)
|
||||
count++;
|
||||
}
|
||||
if (( err < 0)||(handle == 0)) {
|
||||
errorState = QAudio::OpenError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
emit stateChanged(deviceState);
|
||||
return false;
|
||||
}
|
||||
snd_pcm_nonblock( handle, 0 );
|
||||
|
||||
// Step 2: Set the desired HW parameters.
|
||||
snd_pcm_hw_params_alloca( &hwparams );
|
||||
|
||||
bool fatal = false;
|
||||
QString errMessage;
|
||||
unsigned int chunks = 8;
|
||||
|
||||
err = snd_pcm_hw_params_any( handle, hwparams );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_any: err = %1").arg(err);
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_access( handle, hwparams, access );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_access: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = setFormat();
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_format: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)settings.channelCount() );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_channels: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params(handle, hwparams);
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioInput: snd_pcm_hw_params: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if( err < 0) {
|
||||
qWarning()<<errMessage;
|
||||
errorState = QAudio::OpenError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
emit stateChanged(deviceState);
|
||||
return false;
|
||||
}
|
||||
snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
|
||||
buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
|
||||
snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
|
||||
period_size = snd_pcm_frames_to_bytes(handle,period_frames);
|
||||
snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
|
||||
snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
|
||||
|
||||
// Step 3: Set the desired SW parameters.
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
snd_pcm_sw_params_current(handle, swparams);
|
||||
snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
|
||||
snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
|
||||
snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
|
||||
snd_pcm_sw_params(handle, swparams);
|
||||
|
||||
// Step 4: Prepare audio
|
||||
ringBuffer.resize(buffer_size);
|
||||
snd_pcm_prepare( handle );
|
||||
snd_pcm_start(handle);
|
||||
|
||||
// Step 5: Setup timer
|
||||
bytesAvailable = checkBytesReady();
|
||||
|
||||
if(pullMode)
|
||||
connect(audioSource,SIGNAL(readyRead()),this,SLOT(userFeed()));
|
||||
|
||||
// Step 6: Start audio processing
|
||||
chunks = buffer_size/period_size;
|
||||
timer->start(period_time*chunks/2000);
|
||||
|
||||
errorState = QAudio::NoError;
|
||||
|
||||
totalTimeValue = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::close()
|
||||
{
|
||||
timer->stop();
|
||||
|
||||
if ( handle ) {
|
||||
snd_pcm_drop( handle );
|
||||
snd_pcm_close( handle );
|
||||
handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int QAlsaAudioInput::checkBytesReady()
|
||||
{
|
||||
if(resuming)
|
||||
bytesAvailable = period_size;
|
||||
else if(deviceState != QAudio::ActiveState
|
||||
&& deviceState != QAudio::IdleState)
|
||||
bytesAvailable = 0;
|
||||
else {
|
||||
int frames = snd_pcm_avail_update(handle);
|
||||
if (frames < 0) {
|
||||
bytesAvailable = frames;
|
||||
} else {
|
||||
if((int)frames > (int)buffer_frames)
|
||||
frames = buffer_frames;
|
||||
bytesAvailable = snd_pcm_frames_to_bytes(handle, frames);
|
||||
}
|
||||
}
|
||||
return bytesAvailable;
|
||||
}
|
||||
|
||||
int QAlsaAudioInput::bytesReady() const
|
||||
{
|
||||
return qMax(bytesAvailable, 0);
|
||||
}
|
||||
|
||||
qint64 QAlsaAudioInput::read(char* data, qint64 len)
|
||||
{
|
||||
// Read in some audio data and write it to QIODevice, pull mode
|
||||
if ( !handle )
|
||||
return 0;
|
||||
|
||||
int bytesRead = 0;
|
||||
int bytesInRingbufferBeforeRead = ringBuffer.bytesOfDataInBuffer();
|
||||
|
||||
if (ringBuffer.bytesOfDataInBuffer() < len) {
|
||||
|
||||
// bytesAvaiable is saved as a side effect of checkBytesReady().
|
||||
int bytesToRead = checkBytesReady();
|
||||
|
||||
if (bytesToRead < 0) {
|
||||
// bytesAvailable as negative is error code, try to recover from it.
|
||||
xrun_recovery(bytesToRead);
|
||||
bytesToRead = checkBytesReady();
|
||||
if (bytesToRead < 0) {
|
||||
// recovery failed must stop and set error.
|
||||
close();
|
||||
errorState = QAudio::IOError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
emit stateChanged(deviceState);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bytesToRead = qMin<qint64>(len, bytesToRead);
|
||||
bytesToRead = qMin<qint64>(ringBuffer.freeBytes(), bytesToRead);
|
||||
bytesToRead -= bytesToRead % period_size;
|
||||
|
||||
int count=0;
|
||||
int err = 0;
|
||||
while(count < 5 && bytesToRead > 0) {
|
||||
char buffer[bytesToRead];
|
||||
int chunks = bytesToRead / period_size;
|
||||
int frames = chunks * period_frames;
|
||||
if (frames > (int)buffer_frames)
|
||||
frames = buffer_frames;
|
||||
|
||||
int readFrames = snd_pcm_readi(handle, buffer, frames);
|
||||
bytesRead = snd_pcm_frames_to_bytes(handle, readFrames);
|
||||
if (m_volume < 1.0f)
|
||||
QAudioHelperInternal::qMultiplySamples(m_volume, settings, buffer, buffer, bytesRead);
|
||||
|
||||
if (readFrames >= 0) {
|
||||
ringBuffer.write(buffer, bytesRead);
|
||||
#ifdef DEBUG_AUDIO
|
||||
qDebug() << QString::fromLatin1("read in bytes = %1 (frames=%2)").arg(bytesRead).arg(readFrames).toLatin1().constData();
|
||||
#endif
|
||||
break;
|
||||
} else if((readFrames == -EAGAIN) || (readFrames == -EINTR)) {
|
||||
errorState = QAudio::IOError;
|
||||
err = 0;
|
||||
break;
|
||||
} else {
|
||||
if(readFrames == -EPIPE) {
|
||||
errorState = QAudio::UnderrunError;
|
||||
err = snd_pcm_prepare(handle);
|
||||
} else if(readFrames == -ESTRPIPE) {
|
||||
err = snd_pcm_prepare(handle);
|
||||
}
|
||||
if(err != 0) break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bytesRead += bytesInRingbufferBeforeRead;
|
||||
|
||||
if (bytesRead > 0) {
|
||||
// got some send it onward
|
||||
#ifdef DEBUG_AUDIO
|
||||
qDebug() << "frames to write to QIODevice = " <<
|
||||
snd_pcm_bytes_to_frames( handle, (int)bytesRead ) << " (" << bytesRead << ") bytes";
|
||||
#endif
|
||||
if (deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
|
||||
return 0;
|
||||
|
||||
if (pullMode) {
|
||||
qint64 l = 0;
|
||||
qint64 bytesWritten = 0;
|
||||
while (ringBuffer.bytesOfDataInBuffer() > 0) {
|
||||
l = audioSource->write(ringBuffer.availableData(), ringBuffer.availableDataBlockSize());
|
||||
if (l > 0) {
|
||||
ringBuffer.readBytes(l);
|
||||
bytesWritten += l;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (l < 0) {
|
||||
close();
|
||||
errorState = QAudio::IOError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
emit stateChanged(deviceState);
|
||||
} else if (l == 0 && bytesWritten == 0) {
|
||||
if (deviceState != QAudio::IdleState) {
|
||||
errorState = QAudio::NoError;
|
||||
deviceState = QAudio::IdleState;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
} else {
|
||||
bytesAvailable -= bytesWritten;
|
||||
totalTimeValue += bytesWritten;
|
||||
resuming = false;
|
||||
if (deviceState != QAudio::ActiveState) {
|
||||
errorState = QAudio::NoError;
|
||||
deviceState = QAudio::ActiveState;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
} else {
|
||||
while (ringBuffer.bytesOfDataInBuffer() > 0) {
|
||||
int size = ringBuffer.availableDataBlockSize();
|
||||
memcpy(data, ringBuffer.availableData(), size);
|
||||
data += size;
|
||||
ringBuffer.readBytes(size);
|
||||
}
|
||||
|
||||
bytesAvailable -= bytesRead;
|
||||
totalTimeValue += bytesRead;
|
||||
resuming = false;
|
||||
if (deviceState != QAudio::ActiveState) {
|
||||
errorState = QAudio::NoError;
|
||||
deviceState = QAudio::ActiveState;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::resume()
|
||||
{
|
||||
if(deviceState == QAudio::SuspendedState) {
|
||||
int err = 0;
|
||||
|
||||
if(handle) {
|
||||
err = snd_pcm_prepare( handle );
|
||||
if(err < 0)
|
||||
xrun_recovery(err);
|
||||
|
||||
err = snd_pcm_start(handle);
|
||||
if(err < 0)
|
||||
xrun_recovery(err);
|
||||
|
||||
bytesAvailable = buffer_size;
|
||||
}
|
||||
resuming = true;
|
||||
deviceState = QAudio::ActiveState;
|
||||
int chunks = buffer_size/period_size;
|
||||
timer->start(period_time*chunks/2000);
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::setBufferSize(int value)
|
||||
{
|
||||
buffer_size = value;
|
||||
}
|
||||
|
||||
int QAlsaAudioInput::bufferSize() const
|
||||
{
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
int QAlsaAudioInput::periodSize() const
|
||||
{
|
||||
return period_size;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::setNotifyInterval(int ms)
|
||||
{
|
||||
intervalTime = qMax(0, ms);
|
||||
}
|
||||
|
||||
int QAlsaAudioInput::notifyInterval() const
|
||||
{
|
||||
return intervalTime;
|
||||
}
|
||||
|
||||
qint64 QAlsaAudioInput::processedUSecs() const
|
||||
{
|
||||
qint64 result = qint64(1000000) * totalTimeValue /
|
||||
(settings.channelCount()*(settings.sampleSize()/8)) /
|
||||
settings.sampleRate();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::suspend()
|
||||
{
|
||||
if(deviceState == QAudio::ActiveState||resuming) {
|
||||
timer->stop();
|
||||
deviceState = QAudio::SuspendedState;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::userFeed()
|
||||
{
|
||||
if(deviceState == QAudio::StoppedState || deviceState == QAudio::SuspendedState)
|
||||
return;
|
||||
#ifdef DEBUG_AUDIO
|
||||
QTime now(QTime::currentTime());
|
||||
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :userFeed() IN";
|
||||
#endif
|
||||
deviceReady();
|
||||
}
|
||||
|
||||
bool QAlsaAudioInput::deviceReady()
|
||||
{
|
||||
if(pullMode) {
|
||||
// reads some audio data and writes it to QIODevice
|
||||
read(0, buffer_size);
|
||||
} else {
|
||||
// emits readyRead() so user will call read() on QIODevice to get some audio data
|
||||
InputPrivate* a = qobject_cast<InputPrivate*>(audioSource);
|
||||
a->trigger();
|
||||
}
|
||||
bytesAvailable = checkBytesReady();
|
||||
|
||||
if(deviceState != QAudio::ActiveState)
|
||||
return true;
|
||||
|
||||
if (bytesAvailable < 0) {
|
||||
// bytesAvailable as negative is error code, try to recover from it.
|
||||
xrun_recovery(bytesAvailable);
|
||||
bytesAvailable = checkBytesReady();
|
||||
if (bytesAvailable < 0) {
|
||||
// recovery failed must stop and set error.
|
||||
close();
|
||||
errorState = QAudio::IOError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
emit stateChanged(deviceState);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(intervalTime && (timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
|
||||
emit notify();
|
||||
elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
|
||||
timeStamp.restart();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 QAlsaAudioInput::elapsedUSecs() const
|
||||
{
|
||||
if (deviceState == QAudio::StoppedState)
|
||||
return 0;
|
||||
|
||||
return clockStamp.elapsed()*1000;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::reset()
|
||||
{
|
||||
if(handle)
|
||||
snd_pcm_reset(handle);
|
||||
stop();
|
||||
bytesAvailable = 0;
|
||||
}
|
||||
|
||||
void QAlsaAudioInput::drain()
|
||||
{
|
||||
if(handle)
|
||||
snd_pcm_drain(handle);
|
||||
}
|
||||
|
||||
InputPrivate::InputPrivate(QAlsaAudioInput* audio)
|
||||
{
|
||||
audioDevice = qobject_cast<QAlsaAudioInput*>(audio);
|
||||
}
|
||||
|
||||
InputPrivate::~InputPrivate()
|
||||
{
|
||||
}
|
||||
|
||||
qint64 InputPrivate::readData( char* data, qint64 len)
|
||||
{
|
||||
return 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();
|
||||
}
|
||||
|
||||
RingBuffer::RingBuffer() :
|
||||
m_head(0),
|
||||
m_tail(0)
|
||||
{
|
||||
}
|
||||
|
||||
void RingBuffer::resize(int size)
|
||||
{
|
||||
m_data.resize(size);
|
||||
}
|
||||
|
||||
int RingBuffer::bytesOfDataInBuffer() const
|
||||
{
|
||||
if (m_head < m_tail)
|
||||
return m_tail - m_head;
|
||||
else if (m_tail < m_head)
|
||||
return m_data.size() + m_tail - m_head;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RingBuffer::freeBytes() const
|
||||
{
|
||||
if (m_head > m_tail)
|
||||
return m_head - m_tail - 1;
|
||||
else if (m_tail > m_head)
|
||||
return m_data.size() - m_tail + m_head - 1;
|
||||
else
|
||||
return m_data.size() - 1;
|
||||
}
|
||||
|
||||
const char *RingBuffer::availableData() const
|
||||
{
|
||||
return (m_data.constData() + m_head);
|
||||
}
|
||||
|
||||
int RingBuffer::availableDataBlockSize() const
|
||||
{
|
||||
if (m_head > m_tail)
|
||||
return m_data.size() - m_head;
|
||||
else if (m_tail > m_head)
|
||||
return m_tail - m_head;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RingBuffer::readBytes(int bytes)
|
||||
{
|
||||
m_head = (m_head + bytes) % m_data.size();
|
||||
}
|
||||
|
||||
void RingBuffer::write(char *data, int len)
|
||||
{
|
||||
if (m_tail + len < m_data.size()) {
|
||||
memcpy(m_data.data() + m_tail, data, len);
|
||||
m_tail += len;
|
||||
} else {
|
||||
int bytesUntilEnd = m_data.size() - m_tail;
|
||||
memcpy(m_data.data() + m_tail, data, bytesUntilEnd);
|
||||
if (len - bytesUntilEnd > 0)
|
||||
memcpy(m_data.data(), data + bytesUntilEnd, len - bytesUntilEnd);
|
||||
m_tail = len - bytesUntilEnd;
|
||||
}
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include "moc_qalsaaudioinput.cpp"
|
||||
188
src/plugins/alsa/qalsaaudioinput.h
Normal file
188
src/plugins/alsa/qalsaaudioinput.h
Normal file
@@ -0,0 +1,188 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** 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 Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience
|
||||
// of other Qt classes. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
|
||||
#ifndef QAUDIOINPUTALSA_H
|
||||
#define QAUDIOINPUTALSA_H
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <QtCore/qfile.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <QtCore/qstring.h>
|
||||
#include <QtCore/qstringlist.h>
|
||||
#include <QtCore/qdatetime.h>
|
||||
|
||||
#include <QtMultimedia/qaudio.h>
|
||||
#include <QtMultimedia/qaudiodeviceinfo.h>
|
||||
#include <QtMultimedia/qaudiosystem.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
||||
class InputPrivate;
|
||||
|
||||
class RingBuffer
|
||||
{
|
||||
public:
|
||||
RingBuffer();
|
||||
|
||||
void resize(int size);
|
||||
|
||||
int bytesOfDataInBuffer() const;
|
||||
int freeBytes() const;
|
||||
|
||||
const char *availableData() const;
|
||||
int availableDataBlockSize() const;
|
||||
void readBytes(int bytes);
|
||||
|
||||
void write(char *data, int len);
|
||||
|
||||
private:
|
||||
int m_head;
|
||||
int m_tail;
|
||||
|
||||
QByteArray m_data;
|
||||
};
|
||||
|
||||
class QAlsaAudioInput : public QAbstractAudioInput
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QAlsaAudioInput(const QByteArray &device);
|
||||
~QAlsaAudioInput();
|
||||
|
||||
qint64 read(char* data, qint64 len);
|
||||
|
||||
void start(QIODevice* device);
|
||||
QIODevice* start();
|
||||
void stop();
|
||||
void reset();
|
||||
void suspend();
|
||||
void resume();
|
||||
int bytesReady() const;
|
||||
int periodSize() const;
|
||||
void setBufferSize(int value);
|
||||
int bufferSize() const;
|
||||
void setNotifyInterval(int milliSeconds);
|
||||
int notifyInterval() const;
|
||||
qint64 processedUSecs() const;
|
||||
qint64 elapsedUSecs() const;
|
||||
QAudio::Error error() const;
|
||||
QAudio::State state() const;
|
||||
void setFormat(const QAudioFormat& fmt);
|
||||
QAudioFormat format() const;
|
||||
void setVolume(qreal);
|
||||
qreal volume() const;
|
||||
bool resuming;
|
||||
snd_pcm_t* handle;
|
||||
qint64 totalTimeValue;
|
||||
QIODevice* audioSource;
|
||||
QAudioFormat settings;
|
||||
QAudio::Error errorState;
|
||||
QAudio::State deviceState;
|
||||
|
||||
private slots:
|
||||
void userFeed();
|
||||
bool deviceReady();
|
||||
|
||||
private:
|
||||
int checkBytesReady();
|
||||
int xrun_recovery(int err);
|
||||
int setFormat();
|
||||
bool open();
|
||||
void close();
|
||||
void drain();
|
||||
|
||||
QTimer* timer;
|
||||
QTime timeStamp;
|
||||
QTime clockStamp;
|
||||
qint64 elapsedTimeOffset;
|
||||
int intervalTime;
|
||||
RingBuffer ringBuffer;
|
||||
int bytesAvailable;
|
||||
QByteArray m_device;
|
||||
bool pullMode;
|
||||
int buffer_size;
|
||||
int period_size;
|
||||
unsigned int buffer_time;
|
||||
unsigned int period_time;
|
||||
snd_pcm_uframes_t buffer_frames;
|
||||
snd_pcm_uframes_t period_frames;
|
||||
snd_async_handler_t* ahandler;
|
||||
snd_pcm_access_t access;
|
||||
snd_pcm_format_t pcmformat;
|
||||
snd_timestamp_t* timestamp;
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
qreal m_volume;
|
||||
};
|
||||
|
||||
class InputPrivate : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
InputPrivate(QAlsaAudioInput* audio);
|
||||
~InputPrivate();
|
||||
|
||||
qint64 readData( char* data, qint64 len);
|
||||
qint64 writeData(const char* data, qint64 len);
|
||||
|
||||
void trigger();
|
||||
private:
|
||||
QAlsaAudioInput *audioDevice;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
||||
#endif
|
||||
860
src/plugins/alsa/qalsaaudiooutput.cpp
Normal file
860
src/plugins/alsa/qalsaaudiooutput.cpp
Normal file
@@ -0,0 +1,860 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** 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 Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience
|
||||
// of other Qt classes. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// INTERNAL USE ONLY: Do NOT use for any other purpose.
|
||||
//
|
||||
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QtMultimedia/private/qaudiohelpers_p.h>
|
||||
#include "qalsaaudiooutput.h"
|
||||
#include "qalsaaudiodeviceinfo.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
//#define DEBUG_AUDIO 1
|
||||
|
||||
QAlsaAudioOutput::QAlsaAudioOutput(const QByteArray &device)
|
||||
{
|
||||
bytesAvailable = 0;
|
||||
handle = 0;
|
||||
ahandler = 0;
|
||||
access = SND_PCM_ACCESS_RW_INTERLEAVED;
|
||||
pcmformat = SND_PCM_FORMAT_S16;
|
||||
buffer_frames = 0;
|
||||
period_frames = 0;
|
||||
buffer_size = 0;
|
||||
period_size = 0;
|
||||
buffer_time = 100000;
|
||||
period_time = 20000;
|
||||
totalTimeValue = 0;
|
||||
intervalTime = 1000;
|
||||
audioBuffer = 0;
|
||||
errorState = QAudio::NoError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
audioSource = 0;
|
||||
pullMode = true;
|
||||
resuming = false;
|
||||
opened = false;
|
||||
|
||||
m_volume = 1.0f;
|
||||
|
||||
m_device = device;
|
||||
|
||||
timer = new QTimer(this);
|
||||
connect(timer,SIGNAL(timeout()),SLOT(userFeed()));
|
||||
}
|
||||
|
||||
QAlsaAudioOutput::~QAlsaAudioOutput()
|
||||
{
|
||||
close();
|
||||
disconnect(timer, SIGNAL(timeout()));
|
||||
QCoreApplication::processEvents();
|
||||
delete timer;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::setVolume(qreal vol)
|
||||
{
|
||||
m_volume = vol;
|
||||
}
|
||||
|
||||
qreal QAlsaAudioOutput::volume() const
|
||||
{
|
||||
return m_volume;
|
||||
}
|
||||
|
||||
QAudio::Error QAlsaAudioOutput::error() const
|
||||
{
|
||||
return errorState;
|
||||
}
|
||||
|
||||
QAudio::State QAlsaAudioOutput::state() const
|
||||
{
|
||||
return deviceState;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::async_callback(snd_async_handler_t *ahandler)
|
||||
{
|
||||
QAlsaAudioOutput* audioOut;
|
||||
|
||||
audioOut = static_cast<QAlsaAudioOutput*>
|
||||
(snd_async_handler_get_callback_private(ahandler));
|
||||
|
||||
if (audioOut && (audioOut->deviceState == QAudio::ActiveState || audioOut->resuming))
|
||||
audioOut->feedback();
|
||||
}
|
||||
|
||||
int QAlsaAudioOutput::xrun_recovery(int err)
|
||||
{
|
||||
int count = 0;
|
||||
bool reset = false;
|
||||
|
||||
if(err == -EPIPE) {
|
||||
errorState = QAudio::UnderrunError;
|
||||
emit errorChanged(errorState);
|
||||
err = snd_pcm_prepare(handle);
|
||||
if(err < 0)
|
||||
reset = true;
|
||||
|
||||
} else if((err == -ESTRPIPE)||(err == -EIO)) {
|
||||
errorState = QAudio::IOError;
|
||||
emit errorChanged(errorState);
|
||||
while((err = snd_pcm_resume(handle)) == -EAGAIN){
|
||||
usleep(100);
|
||||
count++;
|
||||
if(count > 5) {
|
||||
reset = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(err < 0) {
|
||||
err = snd_pcm_prepare(handle);
|
||||
if(err < 0)
|
||||
reset = true;
|
||||
}
|
||||
}
|
||||
if(reset) {
|
||||
close();
|
||||
open();
|
||||
snd_pcm_prepare(handle);
|
||||
return 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int QAlsaAudioOutput::setFormat()
|
||||
{
|
||||
snd_pcm_format_t pcmformat = SND_PCM_FORMAT_UNKNOWN;
|
||||
|
||||
if(settings.sampleSize() == 8) {
|
||||
pcmformat = SND_PCM_FORMAT_U8;
|
||||
|
||||
} else if(settings.sampleSize() == 16) {
|
||||
if(settings.sampleType() == QAudioFormat::SignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
pcmformat = SND_PCM_FORMAT_S16_LE;
|
||||
else
|
||||
pcmformat = SND_PCM_FORMAT_S16_BE;
|
||||
} else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
pcmformat = SND_PCM_FORMAT_U16_LE;
|
||||
else
|
||||
pcmformat = SND_PCM_FORMAT_U16_BE;
|
||||
}
|
||||
} else if(settings.sampleSize() == 24) {
|
||||
if(settings.sampleType() == QAudioFormat::SignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
pcmformat = SND_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
pcmformat = SND_PCM_FORMAT_S24_BE;
|
||||
} else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
pcmformat = SND_PCM_FORMAT_U24_LE;
|
||||
else
|
||||
pcmformat = SND_PCM_FORMAT_U24_BE;
|
||||
}
|
||||
} else if(settings.sampleSize() == 32) {
|
||||
if(settings.sampleType() == QAudioFormat::SignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
pcmformat = SND_PCM_FORMAT_S32_LE;
|
||||
else
|
||||
pcmformat = SND_PCM_FORMAT_S32_BE;
|
||||
} else if(settings.sampleType() == QAudioFormat::UnSignedInt) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
pcmformat = SND_PCM_FORMAT_U32_LE;
|
||||
else
|
||||
pcmformat = SND_PCM_FORMAT_U32_BE;
|
||||
} else if(settings.sampleType() == QAudioFormat::Float) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
pcmformat = SND_PCM_FORMAT_FLOAT_LE;
|
||||
else
|
||||
pcmformat = SND_PCM_FORMAT_FLOAT_BE;
|
||||
}
|
||||
} else if(settings.sampleSize() == 64) {
|
||||
if(settings.byteOrder() == QAudioFormat::LittleEndian)
|
||||
pcmformat = SND_PCM_FORMAT_FLOAT64_LE;
|
||||
else
|
||||
pcmformat = SND_PCM_FORMAT_FLOAT64_BE;
|
||||
}
|
||||
|
||||
return pcmformat != SND_PCM_FORMAT_UNKNOWN
|
||||
? snd_pcm_hw_params_set_format( handle, hwparams, pcmformat)
|
||||
: -1;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::start(QIODevice* device)
|
||||
{
|
||||
if(deviceState != QAudio::StoppedState)
|
||||
deviceState = QAudio::StoppedState;
|
||||
|
||||
errorState = QAudio::NoError;
|
||||
|
||||
// Handle change of mode
|
||||
if(audioSource && !pullMode) {
|
||||
delete audioSource;
|
||||
audioSource = 0;
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
pullMode = true;
|
||||
audioSource = device;
|
||||
|
||||
deviceState = QAudio::ActiveState;
|
||||
|
||||
open();
|
||||
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
|
||||
QIODevice* QAlsaAudioOutput::start()
|
||||
{
|
||||
if(deviceState != QAudio::StoppedState)
|
||||
deviceState = QAudio::StoppedState;
|
||||
|
||||
errorState = QAudio::NoError;
|
||||
|
||||
// Handle change of mode
|
||||
if(audioSource && !pullMode) {
|
||||
delete audioSource;
|
||||
audioSource = 0;
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
audioSource = new OutputPrivate(this);
|
||||
audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
|
||||
pullMode = false;
|
||||
|
||||
deviceState = QAudio::IdleState;
|
||||
|
||||
open();
|
||||
|
||||
emit stateChanged(deviceState);
|
||||
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::stop()
|
||||
{
|
||||
if(deviceState == QAudio::StoppedState)
|
||||
return;
|
||||
errorState = QAudio::NoError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
close();
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
|
||||
bool QAlsaAudioOutput::open()
|
||||
{
|
||||
if(opened)
|
||||
return true;
|
||||
|
||||
#ifdef DEBUG_AUDIO
|
||||
QTime now(QTime::currentTime());
|
||||
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
|
||||
#endif
|
||||
timeStamp.restart();
|
||||
elapsedTimeOffset = 0;
|
||||
|
||||
int dir;
|
||||
int err = 0;
|
||||
int count=0;
|
||||
unsigned int sampleRate=settings.sampleRate();
|
||||
|
||||
if (!settings.isValid()) {
|
||||
qWarning("QAudioOutput: open error, invalid format.");
|
||||
} else if (settings.sampleRate() <= 0) {
|
||||
qWarning("QAudioOutput: open error, invalid sample rate (%d).",
|
||||
settings.sampleRate());
|
||||
} else {
|
||||
err = -1;
|
||||
}
|
||||
|
||||
if (err == 0) {
|
||||
errorState = QAudio::OpenError;
|
||||
deviceState = QAudio::StoppedState;
|
||||
emit errorChanged(errorState);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString dev = QString(QLatin1String(m_device.constData()));
|
||||
QList<QByteArray> devices = QAlsaAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
|
||||
if(dev.compare(QLatin1String("default")) == 0) {
|
||||
#if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14)
|
||||
if (devices.size() > 0)
|
||||
dev = QLatin1String(devices.first());
|
||||
else
|
||||
return false;
|
||||
#else
|
||||
dev = QLatin1String("hw:0,0");
|
||||
#endif
|
||||
} else {
|
||||
#if(SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR >= 14)
|
||||
dev = QLatin1String(m_device);
|
||||
#else
|
||||
int idx = 0;
|
||||
char *name;
|
||||
|
||||
QString shortName = QLatin1String(m_device.mid(m_device.indexOf('=',0)+1).constData());
|
||||
|
||||
while (snd_card_get_name(idx,&name) == 0) {
|
||||
if(qstrncmp(shortName.toLocal8Bit().constData(),name,shortName.length()) == 0)
|
||||
break;
|
||||
idx++;
|
||||
}
|
||||
dev = QString(QLatin1String("hw:%1,0")).arg(idx);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Step 1: try and open the device
|
||||
while((count < 5) && (err < 0)) {
|
||||
err=snd_pcm_open(&handle,dev.toLocal8Bit().constData(),SND_PCM_STREAM_PLAYBACK,0);
|
||||
if(err < 0)
|
||||
count++;
|
||||
}
|
||||
if (( err < 0)||(handle == 0)) {
|
||||
errorState = QAudio::OpenError;
|
||||
emit errorChanged(errorState);
|
||||
deviceState = QAudio::StoppedState;
|
||||
return false;
|
||||
}
|
||||
snd_pcm_nonblock( handle, 0 );
|
||||
|
||||
// Step 2: Set the desired HW parameters.
|
||||
snd_pcm_hw_params_alloca( &hwparams );
|
||||
|
||||
bool fatal = false;
|
||||
QString errMessage;
|
||||
unsigned int chunks = 8;
|
||||
|
||||
err = snd_pcm_hw_params_any( handle, hwparams );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_any: err = %1").arg(err);
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_rate_resample( handle, hwparams, 1 );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_rate_resample: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_access( handle, hwparams, access );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_access: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = setFormat();
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_format: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_channels( handle, hwparams, (unsigned int)settings.channelCount() );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_channels: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_rate_near( handle, hwparams, &sampleRate, 0 );
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_rate_near: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
unsigned int maxBufferTime = 0;
|
||||
unsigned int minBufferTime = 0;
|
||||
unsigned int maxPeriodTime = 0;
|
||||
unsigned int minPeriodTime = 0;
|
||||
|
||||
err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &maxBufferTime, &dir);
|
||||
if ( err >= 0)
|
||||
err = snd_pcm_hw_params_get_buffer_time_min(hwparams, &minBufferTime, &dir);
|
||||
if ( err >= 0)
|
||||
err = snd_pcm_hw_params_get_period_time_max(hwparams, &maxPeriodTime, &dir);
|
||||
if ( err >= 0)
|
||||
err = snd_pcm_hw_params_get_period_time_min(hwparams, &minPeriodTime, &dir);
|
||||
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: buffer/period min and max: err = %1").arg(err);
|
||||
} else {
|
||||
if (maxBufferTime < buffer_time || buffer_time < minBufferTime || maxPeriodTime < period_time || minPeriodTime > period_time) {
|
||||
#ifdef DEBUG_AUDIO
|
||||
qDebug()<<"defaults out of range";
|
||||
qDebug()<<"pmin="<<minPeriodTime<<", pmax="<<maxPeriodTime<<", bmin="<<minBufferTime<<", bmax="<<maxBufferTime;
|
||||
#endif
|
||||
period_time = minPeriodTime;
|
||||
if (period_time*4 <= maxBufferTime) {
|
||||
// Use 4 periods if possible
|
||||
buffer_time = period_time*4;
|
||||
chunks = 4;
|
||||
} else if (period_time*2 <= maxBufferTime) {
|
||||
// Use 2 periods if possible
|
||||
buffer_time = period_time*2;
|
||||
chunks = 2;
|
||||
} else {
|
||||
qWarning()<<"QAudioOutput: alsa only supports single period!";
|
||||
fatal = true;
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
qDebug()<<"used: buffer_time="<<buffer_time<<", period_time="<<period_time;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, &buffer_time, &dir);
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_buffer_time_near: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_period_time_near(handle, hwparams, &period_time, &dir);
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_period_time_near: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir);
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params_set_periods_near: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if ( !fatal ) {
|
||||
err = snd_pcm_hw_params(handle, hwparams);
|
||||
if ( err < 0 ) {
|
||||
fatal = true;
|
||||
errMessage = QString::fromLatin1("QAudioOutput: snd_pcm_hw_params: err = %1").arg(err);
|
||||
}
|
||||
}
|
||||
if( err < 0) {
|
||||
qWarning()<<errMessage;
|
||||
errorState = QAudio::OpenError;
|
||||
emit errorChanged(errorState);
|
||||
deviceState = QAudio::StoppedState;
|
||||
return false;
|
||||
}
|
||||
snd_pcm_hw_params_get_buffer_size(hwparams,&buffer_frames);
|
||||
buffer_size = snd_pcm_frames_to_bytes(handle,buffer_frames);
|
||||
snd_pcm_hw_params_get_period_size(hwparams,&period_frames, &dir);
|
||||
period_size = snd_pcm_frames_to_bytes(handle,period_frames);
|
||||
snd_pcm_hw_params_get_buffer_time(hwparams,&buffer_time, &dir);
|
||||
snd_pcm_hw_params_get_period_time(hwparams,&period_time, &dir);
|
||||
|
||||
// Step 3: Set the desired SW parameters.
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
snd_pcm_sw_params_current(handle, swparams);
|
||||
snd_pcm_sw_params_set_start_threshold(handle,swparams,period_frames);
|
||||
snd_pcm_sw_params_set_stop_threshold(handle,swparams,buffer_frames);
|
||||
snd_pcm_sw_params_set_avail_min(handle, swparams,period_frames);
|
||||
snd_pcm_sw_params(handle, swparams);
|
||||
|
||||
// Step 4: Prepare audio
|
||||
if(audioBuffer == 0)
|
||||
audioBuffer = new char[snd_pcm_frames_to_bytes(handle,buffer_frames)];
|
||||
snd_pcm_prepare( handle );
|
||||
snd_pcm_start(handle);
|
||||
|
||||
// Step 5: Setup callback and timer fallback
|
||||
snd_async_add_pcm_handler(&ahandler, handle, async_callback, this);
|
||||
bytesAvailable = bytesFree();
|
||||
|
||||
// Step 6: Start audio processing
|
||||
timer->start(period_time/1000);
|
||||
|
||||
clockStamp.restart();
|
||||
timeStamp.restart();
|
||||
elapsedTimeOffset = 0;
|
||||
errorState = QAudio::NoError;
|
||||
totalTimeValue = 0;
|
||||
opened = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::close()
|
||||
{
|
||||
timer->stop();
|
||||
|
||||
if ( handle ) {
|
||||
snd_pcm_drain( handle );
|
||||
snd_pcm_close( handle );
|
||||
handle = 0;
|
||||
delete [] audioBuffer;
|
||||
audioBuffer=0;
|
||||
}
|
||||
if(!pullMode && audioSource) {
|
||||
delete audioSource;
|
||||
audioSource = 0;
|
||||
}
|
||||
opened = false;
|
||||
}
|
||||
|
||||
int QAlsaAudioOutput::bytesFree() const
|
||||
{
|
||||
if(resuming)
|
||||
return period_size;
|
||||
|
||||
if(deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
|
||||
return 0;
|
||||
|
||||
int frames = snd_pcm_avail_update(handle);
|
||||
if (frames == -EPIPE) {
|
||||
// Try and handle buffer underrun
|
||||
int err = snd_pcm_recover(handle, frames, 0);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
else
|
||||
frames = snd_pcm_avail_update(handle);
|
||||
} else if (frames < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((int)frames > (int)buffer_frames)
|
||||
frames = buffer_frames;
|
||||
|
||||
return snd_pcm_frames_to_bytes(handle, frames);
|
||||
}
|
||||
|
||||
qint64 QAlsaAudioOutput::write( const char *data, qint64 len )
|
||||
{
|
||||
// Write out some audio data
|
||||
if ( !handle )
|
||||
return 0;
|
||||
#ifdef DEBUG_AUDIO
|
||||
qDebug()<<"frames to write out = "<<
|
||||
snd_pcm_bytes_to_frames( handle, (int)len )<<" ("<<len<<") bytes";
|
||||
#endif
|
||||
int frames, err;
|
||||
int space = bytesFree();
|
||||
|
||||
if (!space)
|
||||
return 0;
|
||||
|
||||
if (len < space)
|
||||
space = len;
|
||||
|
||||
frames = snd_pcm_bytes_to_frames(handle, space);
|
||||
|
||||
if (m_volume < 1.0f) {
|
||||
char out[space];
|
||||
QAudioHelperInternal::qMultiplySamples(m_volume, settings, data, out, space);
|
||||
err = snd_pcm_writei(handle, out, frames);
|
||||
} else {
|
||||
err = snd_pcm_writei(handle, data, frames);
|
||||
}
|
||||
|
||||
if(err > 0) {
|
||||
totalTimeValue += err;
|
||||
resuming = false;
|
||||
errorState = QAudio::NoError;
|
||||
if (deviceState != QAudio::ActiveState) {
|
||||
deviceState = QAudio::ActiveState;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
return snd_pcm_frames_to_bytes( handle, err );
|
||||
} else
|
||||
err = xrun_recovery(err);
|
||||
|
||||
if(err < 0) {
|
||||
close();
|
||||
errorState = QAudio::FatalError;
|
||||
emit errorChanged(errorState);
|
||||
deviceState = QAudio::StoppedState;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QAlsaAudioOutput::periodSize() const
|
||||
{
|
||||
return period_size;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::setBufferSize(int value)
|
||||
{
|
||||
if(deviceState == QAudio::StoppedState)
|
||||
buffer_size = value;
|
||||
}
|
||||
|
||||
int QAlsaAudioOutput::bufferSize() const
|
||||
{
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::setNotifyInterval(int ms)
|
||||
{
|
||||
intervalTime = qMax(0, ms);
|
||||
}
|
||||
|
||||
int QAlsaAudioOutput::notifyInterval() const
|
||||
{
|
||||
return intervalTime;
|
||||
}
|
||||
|
||||
qint64 QAlsaAudioOutput::processedUSecs() const
|
||||
{
|
||||
return qint64(1000000) * totalTimeValue / settings.sampleRate();
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::resume()
|
||||
{
|
||||
if(deviceState == QAudio::SuspendedState) {
|
||||
int err = 0;
|
||||
|
||||
if(handle) {
|
||||
err = snd_pcm_prepare( handle );
|
||||
if(err < 0)
|
||||
xrun_recovery(err);
|
||||
|
||||
err = snd_pcm_start(handle);
|
||||
if(err < 0)
|
||||
xrun_recovery(err);
|
||||
|
||||
bytesAvailable = (int)snd_pcm_frames_to_bytes(handle, buffer_frames);
|
||||
}
|
||||
resuming = true;
|
||||
|
||||
deviceState = QAudio::ActiveState;
|
||||
|
||||
errorState = QAudio::NoError;
|
||||
timer->start(period_time/1000);
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::setFormat(const QAudioFormat& fmt)
|
||||
{
|
||||
if (deviceState == QAudio::StoppedState)
|
||||
settings = fmt;
|
||||
}
|
||||
|
||||
QAudioFormat QAlsaAudioOutput::format() const
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::suspend()
|
||||
{
|
||||
if(deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState || resuming) {
|
||||
timer->stop();
|
||||
deviceState = QAudio::SuspendedState;
|
||||
errorState = QAudio::NoError;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::userFeed()
|
||||
{
|
||||
if(deviceState == QAudio::StoppedState || deviceState == QAudio::SuspendedState)
|
||||
return;
|
||||
#ifdef DEBUG_AUDIO
|
||||
QTime now(QTime::currentTime());
|
||||
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :userFeed() OUT";
|
||||
#endif
|
||||
if(deviceState == QAudio::IdleState)
|
||||
bytesAvailable = bytesFree();
|
||||
|
||||
deviceReady();
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::feedback()
|
||||
{
|
||||
updateAvailable();
|
||||
}
|
||||
|
||||
|
||||
void QAlsaAudioOutput::updateAvailable()
|
||||
{
|
||||
#ifdef DEBUG_AUDIO
|
||||
QTime now(QTime::currentTime());
|
||||
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :updateAvailable()";
|
||||
#endif
|
||||
bytesAvailable = bytesFree();
|
||||
}
|
||||
|
||||
bool QAlsaAudioOutput::deviceReady()
|
||||
{
|
||||
if(pullMode) {
|
||||
int l = 0;
|
||||
int chunks = bytesAvailable/period_size;
|
||||
if(chunks==0) {
|
||||
bytesAvailable = bytesFree();
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
qDebug()<<"deviceReady() avail="<<bytesAvailable<<" bytes, period size="<<period_size<<" bytes";
|
||||
qDebug()<<"deviceReady() no. of chunks that can fit ="<<chunks<<", chunks in bytes ="<<period_size*chunks;
|
||||
#endif
|
||||
int input = period_frames*chunks;
|
||||
if(input > (int)buffer_frames)
|
||||
input = buffer_frames;
|
||||
l = audioSource->read(audioBuffer,snd_pcm_frames_to_bytes(handle, input));
|
||||
|
||||
// reading can take a while and stream may have been stopped
|
||||
if (!handle)
|
||||
return false;
|
||||
|
||||
if(l > 0) {
|
||||
// Got some data to output
|
||||
if(deviceState != QAudio::ActiveState)
|
||||
return true;
|
||||
qint64 bytesWritten = write(audioBuffer,l);
|
||||
if (bytesWritten != l)
|
||||
audioSource->seek(audioSource->pos()-(l-bytesWritten));
|
||||
bytesAvailable = bytesFree();
|
||||
|
||||
} else if(l == 0) {
|
||||
// Did not get any data to output
|
||||
bytesAvailable = bytesFree();
|
||||
if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
|
||||
// Underrun
|
||||
if (deviceState != QAudio::IdleState) {
|
||||
errorState = QAudio::UnderrunError;
|
||||
emit errorChanged(errorState);
|
||||
deviceState = QAudio::IdleState;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
}
|
||||
|
||||
} else if(l < 0) {
|
||||
close();
|
||||
deviceState = QAudio::StoppedState;
|
||||
errorState = QAudio::IOError;
|
||||
emit errorChanged(errorState);
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
} else {
|
||||
bytesAvailable = bytesFree();
|
||||
if(bytesAvailable > snd_pcm_frames_to_bytes(handle, buffer_frames-period_frames)) {
|
||||
// Underrun
|
||||
if (deviceState != QAudio::IdleState) {
|
||||
errorState = QAudio::UnderrunError;
|
||||
emit errorChanged(errorState);
|
||||
deviceState = QAudio::IdleState;
|
||||
emit stateChanged(deviceState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(deviceState != QAudio::ActiveState)
|
||||
return true;
|
||||
|
||||
if(intervalTime && (timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
|
||||
emit notify();
|
||||
elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
|
||||
timeStamp.restart();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 QAlsaAudioOutput::elapsedUSecs() const
|
||||
{
|
||||
if (deviceState == QAudio::StoppedState)
|
||||
return 0;
|
||||
|
||||
return clockStamp.elapsed()*1000;
|
||||
}
|
||||
|
||||
void QAlsaAudioOutput::reset()
|
||||
{
|
||||
if(handle)
|
||||
snd_pcm_reset(handle);
|
||||
|
||||
stop();
|
||||
}
|
||||
|
||||
OutputPrivate::OutputPrivate(QAlsaAudioOutput* audio)
|
||||
{
|
||||
audioDevice = qobject_cast<QAlsaAudioOutput*>(audio);
|
||||
}
|
||||
|
||||
OutputPrivate::~OutputPrivate() {}
|
||||
|
||||
qint64 OutputPrivate::readData( char* data, qint64 len)
|
||||
{
|
||||
Q_UNUSED(data)
|
||||
Q_UNUSED(len)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 OutputPrivate::writeData(const char* data, qint64 len)
|
||||
{
|
||||
int retry = 0;
|
||||
qint64 written = 0;
|
||||
if((audioDevice->deviceState == QAudio::ActiveState)
|
||||
||(audioDevice->deviceState == QAudio::IdleState)) {
|
||||
while(written < len) {
|
||||
int chunk = audioDevice->write(data+written,(len-written));
|
||||
if(chunk <= 0)
|
||||
retry++;
|
||||
written+=chunk;
|
||||
if(retry > 10)
|
||||
return written;
|
||||
}
|
||||
}
|
||||
return written;
|
||||
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include "moc_qalsaaudiooutput.cpp"
|
||||
170
src/plugins/alsa/qalsaaudiooutput.h
Normal file
170
src/plugins/alsa/qalsaaudiooutput.h
Normal file
@@ -0,0 +1,170 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** 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 Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience
|
||||
// of other Qt classes. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#ifndef QAUDIOOUTPUTALSA_H
|
||||
#define QAUDIOOUTPUTALSA_H
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <QtCore/qfile.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <QtCore/qstring.h>
|
||||
#include <QtCore/qstringlist.h>
|
||||
#include <QtCore/qdatetime.h>
|
||||
|
||||
#include <QtMultimedia/qaudio.h>
|
||||
#include <QtMultimedia/qaudiodeviceinfo.h>
|
||||
#include <QtMultimedia/qaudiosystem.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QAlsaAudioOutput : public QAbstractAudioOutput
|
||||
{
|
||||
friend class OutputPrivate;
|
||||
Q_OBJECT
|
||||
public:
|
||||
QAlsaAudioOutput(const QByteArray &device);
|
||||
~QAlsaAudioOutput();
|
||||
|
||||
qint64 write( const char *data, qint64 len );
|
||||
|
||||
void start(QIODevice* device);
|
||||
QIODevice* start();
|
||||
void stop();
|
||||
void reset();
|
||||
void suspend();
|
||||
void resume();
|
||||
int bytesFree() const;
|
||||
int periodSize() const;
|
||||
void setBufferSize(int value);
|
||||
int bufferSize() const;
|
||||
void setNotifyInterval(int milliSeconds);
|
||||
int notifyInterval() const;
|
||||
qint64 processedUSecs() const;
|
||||
qint64 elapsedUSecs() const;
|
||||
QAudio::Error error() const;
|
||||
QAudio::State state() const;
|
||||
void setFormat(const QAudioFormat& fmt);
|
||||
QAudioFormat format() const;
|
||||
void setVolume(qreal);
|
||||
qreal volume() const;
|
||||
|
||||
|
||||
QIODevice* audioSource;
|
||||
QAudioFormat settings;
|
||||
QAudio::Error errorState;
|
||||
QAudio::State deviceState;
|
||||
|
||||
private slots:
|
||||
void userFeed();
|
||||
void feedback();
|
||||
void updateAvailable();
|
||||
bool deviceReady();
|
||||
|
||||
signals:
|
||||
void processMore();
|
||||
|
||||
private:
|
||||
bool opened;
|
||||
bool pullMode;
|
||||
bool resuming;
|
||||
int buffer_size;
|
||||
int period_size;
|
||||
int intervalTime;
|
||||
qint64 totalTimeValue;
|
||||
unsigned int buffer_time;
|
||||
unsigned int period_time;
|
||||
snd_pcm_uframes_t buffer_frames;
|
||||
snd_pcm_uframes_t period_frames;
|
||||
static void async_callback(snd_async_handler_t *ahandler);
|
||||
int xrun_recovery(int err);
|
||||
|
||||
int setFormat();
|
||||
bool open();
|
||||
void close();
|
||||
|
||||
QTimer* timer;
|
||||
QByteArray m_device;
|
||||
int bytesAvailable;
|
||||
QTime timeStamp;
|
||||
QTime clockStamp;
|
||||
qint64 elapsedTimeOffset;
|
||||
char* audioBuffer;
|
||||
snd_pcm_t* handle;
|
||||
snd_async_handler_t* ahandler;
|
||||
snd_pcm_access_t access;
|
||||
snd_pcm_format_t pcmformat;
|
||||
snd_timestamp_t* timestamp;
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
qreal m_volume;
|
||||
};
|
||||
|
||||
class OutputPrivate : public QIODevice
|
||||
{
|
||||
friend class QAlsaAudioOutput;
|
||||
Q_OBJECT
|
||||
public:
|
||||
OutputPrivate(QAlsaAudioOutput* audio);
|
||||
~OutputPrivate();
|
||||
|
||||
qint64 readData( char* data, qint64 len);
|
||||
qint64 writeData(const char* data, qint64 len);
|
||||
|
||||
private:
|
||||
QAlsaAudioOutput *audioDevice;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
||||
#endif
|
||||
74
src/plugins/alsa/qalsaplugin.cpp
Normal file
74
src/plugins/alsa/qalsaplugin.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** 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 Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qalsaplugin.h"
|
||||
#include "qalsaaudiodeviceinfo.h"
|
||||
#include "qalsaaudioinput.h"
|
||||
#include "qalsaaudiooutput.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QAlsaPlugin::QAlsaPlugin(QObject *parent)
|
||||
: QAudioSystemPlugin(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QList<QByteArray> QAlsaPlugin::availableDevices(QAudio::Mode mode) const
|
||||
{
|
||||
return QAlsaAudioDeviceInfo::availableDevices(mode);
|
||||
}
|
||||
|
||||
QAbstractAudioInput *QAlsaPlugin::createInput(const QByteArray &device)
|
||||
{
|
||||
return new QAlsaAudioInput(device);
|
||||
}
|
||||
|
||||
QAbstractAudioOutput *QAlsaPlugin::createOutput(const QByteArray &device)
|
||||
{
|
||||
return new QAlsaAudioOutput(device);
|
||||
}
|
||||
|
||||
QAbstractAudioDeviceInfo *QAlsaPlugin::createDeviceInfo(const QByteArray &device, QAudio::Mode mode)
|
||||
{
|
||||
return new QAlsaAudioDeviceInfo(device, mode);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
67
src/plugins/alsa/qalsaplugin.h
Normal file
67
src/plugins/alsa/qalsaplugin.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** 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 Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QALSAPLUGIN_H
|
||||
#define QALSAPLUGIN_H
|
||||
|
||||
#include <QtMultimedia/qaudiosystemplugin.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QAlsaPlugin : public QAudioSystemPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.qt.audiosystemfactory/5.0" FILE "alsa.json")
|
||||
|
||||
public:
|
||||
QAlsaPlugin(QObject *parent = 0);
|
||||
~QAlsaPlugin() {}
|
||||
|
||||
QList<QByteArray> availableDevices(QAudio::Mode mode) const Q_DECL_OVERRIDE;
|
||||
QAbstractAudioInput *createInput(const QByteArray &device) Q_DECL_OVERRIDE;
|
||||
QAbstractAudioOutput *createOutput(const QByteArray &device) Q_DECL_OVERRIDE;
|
||||
QAbstractAudioDeviceInfo *createDeviceInfo(const QByteArray &device, QAudio::Mode mode) Q_DECL_OVERRIDE;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QALSAPLUGIN_H
|
||||
Reference in New Issue
Block a user