Move win32 and Alsa audio backends into plugins.

Change-Id: I9835cf5ee97900569f26421a19543b485e933051
Reviewed-by: Yoann Lopes <yoann.lopes@digia.com>
This commit is contained in:
Christian Strømme
2013-11-23 00:14:15 +01:00
committed by The Qt Project
parent 0ab81ef59f
commit 2d54da2d39
23 changed files with 589 additions and 332 deletions

View File

@@ -0,0 +1,3 @@
{
"Keys": ["alsa"]
}

23
src/plugins/alsa/alsa.pro Normal file
View 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

View 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(&params);
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

View 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

View 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"

View 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

View 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"

View 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

View 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

View 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

View File

@@ -22,10 +22,9 @@ qnx:!blackberry {
}
win32 {
SUBDIRS += audiocapture
}
SUBDIRS += audiocapture \
windowsaudio
win32 {
config_directshow: SUBDIRS += directshow
config_wmf: SUBDIRS += wmf
}
@@ -37,12 +36,14 @@ unix:!mac:!android {
SUBDIRS += audiocapture
}
# v4l is turned off because it is not supported in Qt 5
# !maemo*:SUBDIRS += v4l
config_pulseaudio {
SUBDIRS += pulseaudio
} else:config_alsa {
SUBDIRS += alsa
}
# v4l is turned off because it is not supported in Qt 5
# !maemo*:SUBDIRS += v4l
}
mac:!simulator {

View File

@@ -0,0 +1,490 @@
/****************************************************************************
**
** 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/qt_windows.h>
#include <mmsystem.h>
#include "qwindowsaudiodeviceinfo.h"
#if defined(Q_CC_MINGW) && !defined(__MINGW64_VERSION_MAJOR)
struct IBaseFilter; // Needed for strmif.h from stock MinGW.
struct _DDPIXELFORMAT;
typedef struct _DDPIXELFORMAT* LPDDPIXELFORMAT;
#endif
#include <strmif.h>
#if !defined(Q_CC_MINGW) || defined(__MINGW64_VERSION_MAJOR)
# include <uuids.h>
#else
extern GUID CLSID_AudioInputDeviceCategory;
extern GUID CLSID_AudioRendererCategory;
extern GUID IID_ICreateDevEnum;
extern GUID CLSID_SystemDeviceEnum;
#ifndef __ICreateDevEnum_INTERFACE_DEFINED__
#define __ICreateDevEnum_INTERFACE_DEFINED__
DECLARE_INTERFACE_(ICreateDevEnum, IUnknown)
{
STDMETHOD(CreateClassEnumerator)(REFCLSID clsidDeviceClass,
IEnumMoniker **ppEnumMoniker,
DWORD dwFlags) PURE;
};
#endif // __ICreateDevEnum_INTERFACE_DEFINED__
#ifndef __IErrorLog_INTERFACE_DEFINED__
#define __IErrorLog_INTERFACE_DEFINED__
DECLARE_INTERFACE_(IErrorLog, IUnknown)
{
STDMETHOD(AddError)(THIS_ LPCOLESTR, EXCEPINFO *) PURE;
};
#endif /* __IErrorLog_INTERFACE_DEFINED__ */
#ifndef __IPropertyBag_INTERFACE_DEFINED__
#define __IPropertyBag_INTERFACE_DEFINED__
const GUID IID_IPropertyBag = {0x55272A00, 0x42CB, 0x11CE, {0x81, 0x35, 0x00, 0xAA, 0x00, 0x4B, 0xB8, 0x51}};
DECLARE_INTERFACE_(IPropertyBag, IUnknown)
{
STDMETHOD(Read)(THIS_ LPCOLESTR, VARIANT *, IErrorLog *) PURE;
STDMETHOD(Write)(THIS_ LPCOLESTR, VARIANT *) PURE;
};
#endif /* __IPropertyBag_INTERFACE_DEFINED__ */
#endif // defined(Q_CC_MINGW) && !defined(__MINGW64_VERSION_MAJOR)
QT_BEGIN_NAMESPACE
// For mingw toolchain mmsystem.h only defines half the defines, so add if needed.
#ifndef WAVE_FORMAT_44M08
#define WAVE_FORMAT_44M08 0x00000100
#define WAVE_FORMAT_44S08 0x00000200
#define WAVE_FORMAT_44M16 0x00000400
#define WAVE_FORMAT_44S16 0x00000800
#define WAVE_FORMAT_48M08 0x00001000
#define WAVE_FORMAT_48S08 0x00002000
#define WAVE_FORMAT_48M16 0x00004000
#define WAVE_FORMAT_48S16 0x00008000
#define WAVE_FORMAT_96M08 0x00010000
#define WAVE_FORMAT_96S08 0x00020000
#define WAVE_FORMAT_96M16 0x00040000
#define WAVE_FORMAT_96S16 0x00080000
#endif
QWindowsAudioDeviceInfo::QWindowsAudioDeviceInfo(QByteArray dev, QAudio::Mode mode)
{
QDataStream ds(&dev, QIODevice::ReadOnly);
ds >> devId >> device;
this->mode = mode;
updateLists();
}
QWindowsAudioDeviceInfo::~QWindowsAudioDeviceInfo()
{
close();
}
bool QWindowsAudioDeviceInfo::isFormatSupported(const QAudioFormat& format) const
{
return testSettings(format);
}
QAudioFormat QWindowsAudioDeviceInfo::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(11025);
nearest.setChannelCount(1);
nearest.setByteOrder(QAudioFormat::LittleEndian);
nearest.setSampleType(QAudioFormat::SignedInt);
nearest.setSampleSize(8);
nearest.setCodec(QLatin1String("audio/pcm"));
}
return nearest;
}
QString QWindowsAudioDeviceInfo::deviceName() const
{
return device;
}
QStringList QWindowsAudioDeviceInfo::supportedCodecs()
{
updateLists();
return codecz;
}
QList<int> QWindowsAudioDeviceInfo::supportedSampleRates()
{
updateLists();
return sampleRatez;
}
QList<int> QWindowsAudioDeviceInfo::supportedChannelCounts()
{
updateLists();
return channelz;
}
QList<int> QWindowsAudioDeviceInfo::supportedSampleSizes()
{
updateLists();
return sizez;
}
QList<QAudioFormat::Endian> QWindowsAudioDeviceInfo::supportedByteOrders()
{
updateLists();
return byteOrderz;
}
QList<QAudioFormat::SampleType> QWindowsAudioDeviceInfo::supportedSampleTypes()
{
updateLists();
return typez;
}
bool QWindowsAudioDeviceInfo::open()
{
return true;
}
void QWindowsAudioDeviceInfo::close()
{
}
bool QWindowsAudioDeviceInfo::testSettings(const QAudioFormat& format) const
{
// Set nearest to closest settings that do work.
// See if what is in settings will work (return value).
bool failed = false;
bool match = false;
// check codec
for( int i = 0; i < codecz.count(); i++) {
if (format.codec() == codecz.at(i))
match = true;
}
if (!match) failed = true;
// check channel
match = false;
if (!failed) {
for (int i = 0; i < channelz.count(); i++) {
if (format.channelCount() == channelz.at(i)) {
match = true;
break;
}
}
if (!match)
failed = true;
}
// check sampleRate
match = false;
if (!failed) {
for (int i = 0; i < sampleRatez.count(); i++) {
if (format.sampleRate() == sampleRatez.at(i)) {
match = true;
break;
}
}
if (!match)
failed = true;
}
// check sample size
match = false;
if (!failed) {
for( int i = 0; i < sizez.count(); i++) {
if (format.sampleSize() == sizez.at(i)) {
match = true;
break;
}
}
if (!match)
failed = true;
}
// check byte order
match = false;
if (!failed) {
for( int i = 0; i < byteOrderz.count(); i++) {
if (format.byteOrder() == byteOrderz.at(i)) {
match = true;
break;
}
}
if (!match)
failed = true;
}
// check sample type
match = false;
if (!failed) {
for( int i = 0; i < typez.count(); i++) {
if (format.sampleType() == typez.at(i)) {
match = true;
break;
}
}
if (!match)
failed = true;
}
if(!failed) {
// settings work
return true;
}
return false;
}
void QWindowsAudioDeviceInfo::updateLists()
{
// redo all lists based on current settings
bool match = false;
DWORD fmt = 0;
if(mode == QAudio::AudioOutput) {
WAVEOUTCAPS woc;
if (waveOutGetDevCaps(devId, &woc, sizeof(WAVEOUTCAPS)) == MMSYSERR_NOERROR) {
match = true;
fmt = woc.dwFormats;
}
} else {
WAVEINCAPS woc;
if (waveInGetDevCaps(devId, &woc, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR) {
match = true;
fmt = woc.dwFormats;
}
}
sizez.clear();
sampleRatez.clear();
channelz.clear();
byteOrderz.clear();
typez.clear();
codecz.clear();
if(match) {
if ((fmt & WAVE_FORMAT_1M08)
|| (fmt & WAVE_FORMAT_1S08)
|| (fmt & WAVE_FORMAT_2M08)
|| (fmt & WAVE_FORMAT_2S08)
|| (fmt & WAVE_FORMAT_4M08)
|| (fmt & WAVE_FORMAT_4S08)
|| (fmt & WAVE_FORMAT_48M08)
|| (fmt & WAVE_FORMAT_48S08)
|| (fmt & WAVE_FORMAT_96M08)
|| (fmt & WAVE_FORMAT_96S08)
) {
sizez.append(8);
}
if ((fmt & WAVE_FORMAT_1M16)
|| (fmt & WAVE_FORMAT_1S16)
|| (fmt & WAVE_FORMAT_2M16)
|| (fmt & WAVE_FORMAT_2S16)
|| (fmt & WAVE_FORMAT_4M16)
|| (fmt & WAVE_FORMAT_4S16)
|| (fmt & WAVE_FORMAT_48M16)
|| (fmt & WAVE_FORMAT_48S16)
|| (fmt & WAVE_FORMAT_96M16)
|| (fmt & WAVE_FORMAT_96S16)
) {
sizez.append(16);
}
if ((fmt & WAVE_FORMAT_1M08)
|| (fmt & WAVE_FORMAT_1S08)
|| (fmt & WAVE_FORMAT_1M16)
|| (fmt & WAVE_FORMAT_1S16)) {
sampleRatez.append(11025);
}
if ((fmt & WAVE_FORMAT_2M08)
|| (fmt & WAVE_FORMAT_2S08)
|| (fmt & WAVE_FORMAT_2M16)
|| (fmt & WAVE_FORMAT_2S16)) {
sampleRatez.append(22050);
}
if ((fmt & WAVE_FORMAT_4M08)
|| (fmt & WAVE_FORMAT_4S08)
|| (fmt & WAVE_FORMAT_4M16)
|| (fmt & WAVE_FORMAT_4S16)) {
sampleRatez.append(44100);
}
if ((fmt & WAVE_FORMAT_48M08)
|| (fmt & WAVE_FORMAT_48S08)
|| (fmt & WAVE_FORMAT_48M16)
|| (fmt & WAVE_FORMAT_48S16)) {
sampleRatez.append(48000);
}
if ((fmt & WAVE_FORMAT_96M08)
|| (fmt & WAVE_FORMAT_96S08)
|| (fmt & WAVE_FORMAT_96M16)
|| (fmt & WAVE_FORMAT_96S16)) {
sampleRatez.append(96000);
}
channelz.append(1);
channelz.append(2);
if (mode == QAudio::AudioOutput) {
channelz.append(4);
channelz.append(6);
channelz.append(8);
}
byteOrderz.append(QAudioFormat::LittleEndian);
typez.append(QAudioFormat::SignedInt);
typez.append(QAudioFormat::UnSignedInt);
codecz.append(QLatin1String("audio/pcm"));
}
if (sampleRatez.count() > 0)
sampleRatez.prepend(8000);
}
QList<QByteArray> QWindowsAudioDeviceInfo::availableDevices(QAudio::Mode mode)
{
Q_UNUSED(mode)
QList<QByteArray> devices;
//enumerate device fullnames through directshow api
CoInitialize(NULL);
ICreateDevEnum *pDevEnum = NULL;
IEnumMoniker *pEnum = NULL;
// Create the System device enumerator
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
reinterpret_cast<void **>(&pDevEnum));
unsigned long iNumDevs = mode == QAudio::AudioOutput ? waveOutGetNumDevs() : waveInGetNumDevs();
if (SUCCEEDED(hr)) {
// Create the enumerator for the audio input/output category
if (pDevEnum->CreateClassEnumerator(
mode == QAudio::AudioOutput ? CLSID_AudioRendererCategory : CLSID_AudioInputDeviceCategory,
&pEnum, 0) == S_OK) {
pEnum->Reset();
// go through and find all audio devices
IMoniker *pMoniker = NULL;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK) {
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(0,0,IID_IPropertyBag,
reinterpret_cast<void **>(&pPropBag));
if (FAILED(hr)) {
pMoniker->Release();
continue; // skip this one
}
// Find if it is a wave device
VARIANT var;
VariantInit(&var);
hr = pPropBag->Read(mode == QAudio::AudioOutput ? L"WaveOutID" : L"WaveInID", &var, 0);
if (SUCCEEDED(hr)) {
LONG waveID = var.lVal;
if (waveID >= 0 && waveID < LONG(iNumDevs)) {
VariantClear(&var);
// Find the description
hr = pPropBag->Read(L"FriendlyName", &var, 0);
if (SUCCEEDED(hr)) {
QByteArray device;
QDataStream ds(&device, QIODevice::WriteOnly);
ds << quint32(waveID) << QString::fromWCharArray(var.bstrVal);
devices.append(device);
}
}
}
pPropBag->Release();
pMoniker->Release();
}
}
}
CoUninitialize();
return devices;
}
QByteArray QWindowsAudioDeviceInfo::defaultOutputDevice()
{
QByteArray defaultDevice;
QDataStream ds(&defaultDevice, QIODevice::WriteOnly);
ds << quint32(WAVE_MAPPER) // device ID for default device
<< QStringLiteral("Default Output Device");
return defaultDevice;
}
QByteArray QWindowsAudioDeviceInfo::defaultInputDevice()
{
QByteArray defaultDevice;
QDataStream ds(&defaultDevice, QIODevice::WriteOnly);
ds << quint32(WAVE_MAPPER) // device ID for default device
<< QStringLiteral("Default Input Device");
return defaultDevice;
}
QT_END_NAMESPACE

View File

@@ -0,0 +1,114 @@
/****************************************************************************
**
** 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 QWINDOWSAUDIODEVICEINFO_H
#define QWINDOWSAUDIODEVICEINFO_H
#include <QtCore/qbytearray.h>
#include <QtCore/qstringlist.h>
#include <QtCore/qlist.h>
#include <QtCore/qdebug.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 QWindowsAudioDeviceInfo : public QAbstractAudioDeviceInfo
{
Q_OBJECT
public:
QWindowsAudioDeviceInfo(QByteArray dev,QAudio::Mode mode);
~QWindowsAudioDeviceInfo();
bool open();
void close();
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:
QAudio::Mode mode;
QString device;
quint32 devId;
QAudioFormat nearest;
QList<int> sampleRatez;
QList<int> channelz;
QList<int> sizez;
QList<QAudioFormat::Endian> byteOrderz;
QStringList codecz;
QList<QAudioFormat::SampleType> typez;
};
QT_END_NAMESPACE
#endif // QWINDOWSAUDIODEVICEINFO_H

View File

@@ -0,0 +1,752 @@
/****************************************************************************
**
** 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 "qwindowsaudioinput.h"
QT_BEGIN_NAMESPACE
//#define DEBUG_AUDIO 1
QWindowsAudioInput::QWindowsAudioInput(const QByteArray &device)
{
bytesAvailable = 0;
buffer_size = 0;
period_size = 0;
m_device = device;
totalTimeValue = 0;
intervalTime = 1000;
errorState = QAudio::NoError;
deviceState = QAudio::StoppedState;
audioSource = 0;
pullMode = true;
resuming = false;
finished = false;
waveBlockOffset = 0;
mixerID = 0;
memset(&mixerLineControls, 0, sizeof(mixerLineControls));
initMixer();
}
QWindowsAudioInput::~QWindowsAudioInput()
{
stop();
closeMixer();
}
void QT_WIN_CALLBACK QWindowsAudioInput::waveInProc( HWAVEIN hWaveIn, UINT uMsg,
DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 )
{
Q_UNUSED(dwParam1)
Q_UNUSED(dwParam2)
Q_UNUSED(hWaveIn)
QWindowsAudioInput* qAudio;
qAudio = (QWindowsAudioInput*)(dwInstance);
if(!qAudio)
return;
QMutexLocker(&qAudio->mutex);
switch(uMsg) {
case WIM_OPEN:
break;
case WIM_DATA:
if(qAudio->waveFreeBlockCount > 0)
qAudio->waveFreeBlockCount--;
qAudio->feedback();
break;
case WIM_CLOSE:
qAudio->finished = true;
break;
default:
return;
}
}
WAVEHDR* QWindowsAudioInput::allocateBlocks(int size, int count)
{
int i;
unsigned char* buffer;
WAVEHDR* blocks;
DWORD totalBufferSize = (size + sizeof(WAVEHDR))*count;
if((buffer=(unsigned char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
totalBufferSize)) == 0) {
qWarning("QAudioInput: Memory allocation error");
return 0;
}
blocks = (WAVEHDR*)buffer;
buffer += sizeof(WAVEHDR)*count;
for(i = 0; i < count; i++) {
blocks[i].dwBufferLength = size;
blocks[i].lpData = (LPSTR)buffer;
blocks[i].dwBytesRecorded=0;
blocks[i].dwUser = 0L;
blocks[i].dwFlags = 0L;
blocks[i].dwLoops = 0L;
result = waveInPrepareHeader(hWaveIn,&blocks[i], sizeof(WAVEHDR));
if(result != MMSYSERR_NOERROR) {
qWarning("QAudioInput: Can't prepare block %d",i);
return 0;
}
buffer += size;
}
return blocks;
}
void QWindowsAudioInput::freeBlocks(WAVEHDR* blockArray)
{
WAVEHDR* blocks = blockArray;
int count = buffer_size/period_size;
for(int i = 0; i < count; i++) {
waveInUnprepareHeader(hWaveIn,blocks, sizeof(WAVEHDR));
blocks++;
}
HeapFree(GetProcessHeap(), 0, blockArray);
}
QAudio::Error QWindowsAudioInput::error() const
{
return errorState;
}
QAudio::State QWindowsAudioInput::state() const
{
return deviceState;
}
#ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET
#ifndef DRVM_MAPPER
#define DRVM_MAPPER 0x2000
#endif
#ifndef DRVM_MAPPER_STATUS
#define DRVM_MAPPER_STATUS (DRVM_MAPPER+0)
#endif
#define DRVM_USER 0x4000
#define DRVM_MAPPER_RECONFIGURE (DRVM_MAPPER+1)
#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER+21)
#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER+23)
#endif
void QWindowsAudioInput::setVolume(qreal volume)
{
for (DWORD i = 0; i < mixerLineControls.cControls; i++) {
MIXERCONTROLDETAILS controlDetails;
controlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
controlDetails.dwControlID = mixerLineControls.pamxctrl[i].dwControlID;
controlDetails.cChannels = 1;
if ((mixerLineControls.pamxctrl[i].dwControlType == MIXERCONTROL_CONTROLTYPE_FADER) ||
(mixerLineControls.pamxctrl[i].dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)) {
MIXERCONTROLDETAILS_UNSIGNED controlDetailsUnsigned;
controlDetailsUnsigned.dwValue = qBound(DWORD(0), DWORD(65535.0 * volume + 0.5), DWORD(65535));
controlDetails.cMultipleItems = 0;
controlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
controlDetails.paDetails = &controlDetailsUnsigned;
mixerSetControlDetails(mixerID, &controlDetails, MIXER_SETCONTROLDETAILSF_VALUE);
}
}
}
qreal QWindowsAudioInput::volume() const
{
DWORD volume = 0;
for (DWORD i = 0; i < mixerLineControls.cControls; i++) {
if ((mixerLineControls.pamxctrl[i].dwControlType != MIXERCONTROL_CONTROLTYPE_FADER) &&
(mixerLineControls.pamxctrl[i].dwControlType != MIXERCONTROL_CONTROLTYPE_VOLUME)) {
continue;
}
MIXERCONTROLDETAILS controlDetails;
controlDetails.cbStruct = sizeof(controlDetails);
controlDetails.dwControlID = mixerLineControls.pamxctrl[i].dwControlID;
controlDetails.cChannels = 1;
controlDetails.cMultipleItems = 0;
controlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
MIXERCONTROLDETAILS_UNSIGNED detailsUnsigned;
controlDetails.paDetails = &detailsUnsigned;
memset(controlDetails.paDetails, 0, controlDetails.cbDetails);
MMRESULT result = mixerGetControlDetails(mixerID, &controlDetails, MIXER_GETCONTROLDETAILSF_VALUE);
if (result != MMSYSERR_NOERROR)
continue;
if (controlDetails.cbDetails < sizeof(MIXERCONTROLDETAILS_UNSIGNED))
continue;
volume = detailsUnsigned.dwValue;
break;
}
return volume / 65535.0;
}
void QWindowsAudioInput::setFormat(const QAudioFormat& fmt)
{
if (deviceState == QAudio::StoppedState)
settings = fmt;
}
QAudioFormat QWindowsAudioInput::format() const
{
return settings;
}
void QWindowsAudioInput::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* QWindowsAudioInput::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 QWindowsAudioInput::stop()
{
if(deviceState == QAudio::StoppedState)
return;
close();
emit stateChanged(deviceState);
}
bool QWindowsAudioInput::open()
{
#ifdef DEBUG_AUDIO
QTime now(QTime::currentTime());
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
#endif
header = 0;
period_size = 0;
if (!settings.isValid()) {
qWarning("QAudioInput: open error, invalid format.");
} else if (settings.channelCount() <= 0) {
qWarning("QAudioInput: open error, invalid number of channels (%d).",
settings.channelCount());
} else if (settings.sampleSize() <= 0) {
qWarning("QAudioInput: open error, invalid sample size (%d).",
settings.sampleSize());
} else if (settings.sampleRate() < 8000 || settings.sampleRate() > 96000) {
qWarning("QAudioInput: open error, sample rate out of range (%d).", settings.sampleRate());
} else if (buffer_size == 0) {
buffer_size
= (settings.sampleRate()
* settings.channelCount()
* settings.sampleSize()
+ 39) / 40;
period_size = buffer_size / 5;
} else {
period_size = buffer_size / 5;
}
if (period_size == 0) {
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
return false;
}
timeStamp.restart();
elapsedTimeOffset = 0;
wfx.nSamplesPerSec = settings.sampleRate();
wfx.wBitsPerSample = settings.sampleSize();
wfx.nChannels = settings.channelCount();
wfx.cbSize = 0;
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
QDataStream ds(&m_device, QIODevice::ReadOnly);
quint32 deviceId;
ds >> deviceId;
if (waveInOpen(&hWaveIn, UINT_PTR(deviceId), &wfx,
(DWORD_PTR)&waveInProc,
(DWORD_PTR) this,
CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
qWarning("QAudioInput: failed to open audio device");
return false;
}
waveBlocks = allocateBlocks(period_size, buffer_size/period_size);
waveBlockOffset = 0;
if(waveBlocks == 0) {
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
qWarning("QAudioInput: failed to allocate blocks. open failed");
return false;
}
mutex.lock();
waveFreeBlockCount = buffer_size/period_size;
mutex.unlock();
for(int i=0; i<buffer_size/period_size; i++) {
result = waveInAddBuffer(hWaveIn, &waveBlocks[i], sizeof(WAVEHDR));
if(result != MMSYSERR_NOERROR) {
qWarning("QAudioInput: failed to setup block %d,err=%d",i,result);
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
return false;
}
}
result = waveInStart(hWaveIn);
if(result) {
qWarning("QAudioInput: failed to start audio input");
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
return false;
}
timeStampOpened.restart();
elapsedTimeOffset = 0;
totalTimeValue = 0;
errorState = QAudio::NoError;
return true;
}
void QWindowsAudioInput::close()
{
if(deviceState == QAudio::StoppedState)
return;
deviceState = QAudio::StoppedState;
waveInReset(hWaveIn);
mutex.lock();
for (int i=0; i<waveFreeBlockCount; i++)
waveInUnprepareHeader(hWaveIn,&waveBlocks[i],sizeof(WAVEHDR));
freeBlocks(waveBlocks);
mutex.unlock();
waveInClose(hWaveIn);
int count = 0;
while(!finished && count < 500) {
count++;
Sleep(10);
}
}
void QWindowsAudioInput::initMixer()
{
QDataStream ds(&m_device, QIODevice::ReadOnly);
quint32 inputDevice;
ds >> inputDevice;
if (int(inputDevice) < 0)
return;
// Get the Mixer ID from the Sound Device ID
UINT mixerIntID = 0;
if (mixerGetID((HMIXEROBJ)(quintptr(inputDevice)), &mixerIntID, MIXER_OBJECTF_WAVEIN) != MMSYSERR_NOERROR)
return;
mixerID = (HMIXEROBJ)mixerIntID;
// Get the Destination (Recording) Line Infomation
MIXERLINE mixerLine;
mixerLine.cbStruct = sizeof(MIXERLINE);
mixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
if (mixerGetLineInfo(mixerID, &mixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE) != MMSYSERR_NOERROR)
return;
// Set all the Destination (Recording) Line Controls
if (mixerLine.cControls > 0) {
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
mixerLineControls.dwLineID = mixerLine.dwLineID;
mixerLineControls.cControls = mixerLine.cControls;
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
mixerLineControls.pamxctrl = new MIXERCONTROL[mixerLineControls.cControls];
if (mixerGetLineControls(mixerID, &mixerLineControls, MIXER_GETLINECONTROLSF_ALL) != MMSYSERR_NOERROR)
closeMixer();
}
}
void QWindowsAudioInput::closeMixer()
{
delete[] mixerLineControls.pamxctrl;
memset(&mixerLineControls, 0, sizeof(mixerLineControls));
}
int QWindowsAudioInput::bytesReady() const
{
if(period_size == 0 || buffer_size == 0)
return 0;
int buf = ((buffer_size/period_size)-waveFreeBlockCount)*period_size;
if(buf < 0)
buf = 0;
return buf;
}
qint64 QWindowsAudioInput::read(char* data, qint64 len)
{
bool done = false;
char* p = data;
qint64 l = 0;
qint64 written = 0;
while(!done) {
// Read in some audio data
if(waveBlocks[header].dwBytesRecorded > 0 && waveBlocks[header].dwFlags & WHDR_DONE) {
if(pullMode) {
l = audioSource->write(waveBlocks[header].lpData + waveBlockOffset,
waveBlocks[header].dwBytesRecorded - waveBlockOffset);
#ifdef DEBUG_AUDIO
qDebug()<<"IN: "<<waveBlocks[header].dwBytesRecorded<<", OUT: "<<l;
#endif
if(l < 0) {
// error
qWarning("QAudioInput: IOError");
errorState = QAudio::IOError;
} else if(l == 0) {
// cant write to IODevice
qWarning("QAudioInput: IOError, can't write to QIODevice");
errorState = QAudio::IOError;
} else {
totalTimeValue += l;
errorState = QAudio::NoError;
if (deviceState != QAudio::ActiveState) {
deviceState = QAudio::ActiveState;
emit stateChanged(deviceState);
}
resuming = false;
}
} else {
l = qMin<qint64>(len, waveBlocks[header].dwBytesRecorded - waveBlockOffset);
// push mode
memcpy(p, waveBlocks[header].lpData + waveBlockOffset, l);
len -= l;
#ifdef DEBUG_AUDIO
qDebug()<<"IN: "<<waveBlocks[header].dwBytesRecorded<<", OUT: "<<l;
#endif
totalTimeValue += l;
errorState = QAudio::NoError;
if (deviceState != QAudio::ActiveState) {
deviceState = QAudio::ActiveState;
emit stateChanged(deviceState);
}
resuming = false;
}
} else {
//no data, not ready yet, next time
break;
}
if (l < waveBlocks[header].dwBytesRecorded - waveBlockOffset) {
waveBlockOffset += l;
done = true;
} else {
waveBlockOffset = 0;
waveInUnprepareHeader(hWaveIn,&waveBlocks[header], sizeof(WAVEHDR));
mutex.lock();
waveFreeBlockCount++;
mutex.unlock();
waveBlocks[header].dwBytesRecorded=0;
waveBlocks[header].dwFlags = 0L;
result = waveInPrepareHeader(hWaveIn,&waveBlocks[header], sizeof(WAVEHDR));
if(result != MMSYSERR_NOERROR) {
result = waveInPrepareHeader(hWaveIn,&waveBlocks[header], sizeof(WAVEHDR));
qWarning("QAudioInput: failed to prepare block %d,err=%d",header,result);
errorState = QAudio::IOError;
mutex.lock();
waveFreeBlockCount--;
mutex.unlock();
return 0;
}
result = waveInAddBuffer(hWaveIn, &waveBlocks[header], sizeof(WAVEHDR));
if(result != MMSYSERR_NOERROR) {
qWarning("QAudioInput: failed to setup block %d,err=%d",header,result);
errorState = QAudio::IOError;
mutex.lock();
waveFreeBlockCount--;
mutex.unlock();
return 0;
}
header++;
if(header >= buffer_size/period_size)
header = 0;
p+=l;
mutex.lock();
if(!pullMode) {
if(len < period_size || waveFreeBlockCount == buffer_size/period_size)
done = true;
} else {
if(waveFreeBlockCount == buffer_size/period_size)
done = true;
}
mutex.unlock();
}
written+=l;
}
#ifdef DEBUG_AUDIO
qDebug()<<"read in len="<<written;
#endif
return written;
}
void QWindowsAudioInput::resume()
{
if(deviceState == QAudio::SuspendedState) {
deviceState = QAudio::ActiveState;
for(int i=0; i<buffer_size/period_size; i++) {
result = waveInAddBuffer(hWaveIn, &waveBlocks[i], sizeof(WAVEHDR));
if(result != MMSYSERR_NOERROR) {
qWarning("QAudioInput: failed to setup block %d,err=%d",i,result);
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
return;
}
}
mutex.lock();
waveFreeBlockCount = buffer_size/period_size;
mutex.unlock();
header = 0;
resuming = true;
waveBlockOffset = 0;
waveInStart(hWaveIn);
QTimer::singleShot(20,this,SLOT(feedback()));
emit stateChanged(deviceState);
}
}
void QWindowsAudioInput::setBufferSize(int value)
{
buffer_size = value;
}
int QWindowsAudioInput::bufferSize() const
{
return buffer_size;
}
int QWindowsAudioInput::periodSize() const
{
return period_size;
}
void QWindowsAudioInput::setNotifyInterval(int ms)
{
intervalTime = qMax(0, ms);
}
int QWindowsAudioInput::notifyInterval() const
{
return intervalTime;
}
qint64 QWindowsAudioInput::processedUSecs() const
{
if (deviceState == QAudio::StoppedState)
return 0;
qint64 result = qint64(1000000) * totalTimeValue /
(settings.channelCount()*(settings.sampleSize()/8)) /
settings.sampleRate();
return result;
}
void QWindowsAudioInput::suspend()
{
if(deviceState == QAudio::ActiveState) {
waveInReset(hWaveIn);
deviceState = QAudio::SuspendedState;
emit stateChanged(deviceState);
}
}
void QWindowsAudioInput::feedback()
{
#ifdef DEBUG_AUDIO
QTime now(QTime::currentTime());
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :feedback() INPUT "<<this;
#endif
if(!(deviceState==QAudio::StoppedState||deviceState==QAudio::SuspendedState))
QMetaObject::invokeMethod(this, "deviceReady", Qt::QueuedConnection);
}
bool QWindowsAudioInput::deviceReady()
{
bytesAvailable = bytesReady();
#ifdef DEBUG_AUDIO
QTime now(QTime::currentTime());
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :deviceReady() INPUT";
#endif
if(deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
return true;
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();
}
if(intervalTime && (timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
emit notify();
elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
timeStamp.restart();
}
return true;
}
qint64 QWindowsAudioInput::elapsedUSecs() const
{
if (deviceState == QAudio::StoppedState)
return 0;
return timeStampOpened.elapsed()*1000;
}
void QWindowsAudioInput::reset()
{
stop();
if (period_size > 0)
waveFreeBlockCount = buffer_size / period_size;
}
InputPrivate::InputPrivate(QWindowsAudioInput* audio)
{
audioDevice = qobject_cast<QWindowsAudioInput*>(audio);
}
InputPrivate::~InputPrivate() {}
qint64 InputPrivate::readData( char* data, qint64 len)
{
// push mode, user read() called
if(audioDevice->deviceState != QAudio::ActiveState &&
audioDevice->deviceState != QAudio::IdleState)
return 0;
// Read in some audio data
return audioDevice->read(data,len);
}
qint64 InputPrivate::writeData(const char* data, qint64 len)
{
Q_UNUSED(data)
Q_UNUSED(len)
emit readyRead();
return 0;
}
void InputPrivate::trigger()
{
emit readyRead();
}
QT_END_NAMESPACE
#include "moc_qwindowsaudioinput.cpp"

View File

@@ -0,0 +1,179 @@
/****************************************************************************
**
** 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 QWINDOWSAUDIOINPUT_H
#define QWINDOWSAUDIOINPUT_H
#include <QtCore/qt_windows.h>
#include <mmsystem.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 <QtCore/qmutex.h>
#include <QtMultimedia/qaudio.h>
#include <QtMultimedia/qaudiodeviceinfo.h>
#include <QtMultimedia/qaudiosystem.h>
QT_BEGIN_NAMESPACE
// For compat with 4.6
#if !defined(QT_WIN_CALLBACK)
# if defined(Q_CC_MINGW)
# define QT_WIN_CALLBACK CALLBACK __attribute__ ((force_align_arg_pointer))
# else
# define QT_WIN_CALLBACK CALLBACK
# endif
#endif
class QWindowsAudioInput : public QAbstractAudioInput
{
Q_OBJECT
public:
QWindowsAudioInput(const QByteArray &device);
~QWindowsAudioInput();
qint64 read(char* data, qint64 len);
void setFormat(const QAudioFormat& fmt);
QAudioFormat format() const;
QIODevice* start();
void start(QIODevice* device);
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 setVolume(qreal volume);
qreal volume() const;
QIODevice* audioSource;
QAudioFormat settings;
QAudio::Error errorState;
QAudio::State deviceState;
private:
qint32 buffer_size;
qint32 period_size;
qint32 header;
QByteArray m_device;
int bytesAvailable;
int intervalTime;
QTime timeStamp;
qint64 elapsedTimeOffset;
QTime timeStampOpened;
qint64 totalTimeValue;
bool pullMode;
bool resuming;
WAVEFORMATEX wfx;
HWAVEIN hWaveIn;
MMRESULT result;
WAVEHDR* waveBlocks;
volatile bool finished;
volatile int waveFreeBlockCount;
int waveBlockOffset;
QMutex mutex;
static void QT_WIN_CALLBACK waveInProc( HWAVEIN hWaveIn, UINT uMsg,
DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 );
WAVEHDR* allocateBlocks(int size, int count);
void freeBlocks(WAVEHDR* blockArray);
bool open();
void close();
void initMixer();
void closeMixer();
HMIXEROBJ mixerID;
MIXERLINECONTROLS mixerLineControls;
private slots:
void feedback();
bool deviceReady();
signals:
void processMore();
};
class InputPrivate : public QIODevice
{
Q_OBJECT
public:
InputPrivate(QWindowsAudioInput* audio);
~InputPrivate();
qint64 readData( char* data, qint64 len);
qint64 writeData(const char* data, qint64 len);
void trigger();
private:
QWindowsAudioInput *audioDevice;
};
QT_END_NAMESPACE
#endif

View File

@@ -0,0 +1,762 @@
/****************************************************************************
**
** 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 "qwindowsaudiooutput.h"
#include <QtEndian>
#ifndef SPEAKER_FRONT_LEFT
#define SPEAKER_FRONT_LEFT 0x00000001
#define SPEAKER_FRONT_RIGHT 0x00000002
#define SPEAKER_FRONT_CENTER 0x00000004
#define SPEAKER_LOW_FREQUENCY 0x00000008
#define SPEAKER_BACK_LEFT 0x00000010
#define SPEAKER_BACK_RIGHT 0x00000020
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080
#define SPEAKER_BACK_CENTER 0x00000100
#define SPEAKER_SIDE_LEFT 0x00000200
#define SPEAKER_SIDE_RIGHT 0x00000400
#define SPEAKER_TOP_CENTER 0x00000800
#define SPEAKER_TOP_FRONT_LEFT 0x00001000
#define SPEAKER_TOP_FRONT_CENTER 0x00002000
#define SPEAKER_TOP_FRONT_RIGHT 0x00004000
#define SPEAKER_TOP_BACK_LEFT 0x00008000
#define SPEAKER_TOP_BACK_CENTER 0x00010000
#define SPEAKER_TOP_BACK_RIGHT 0x00020000
#define SPEAKER_RESERVED 0x7FFC0000
#define SPEAKER_ALL 0x80000000
#endif
#ifndef _WAVEFORMATEXTENSIBLE_
#define _WAVEFORMATEXTENSIBLE_
typedef struct
{
WAVEFORMATEX Format; // Base WAVEFORMATEX data
union
{
WORD wValidBitsPerSample; // Valid bits in each sample container
WORD wSamplesPerBlock; // Samples per block of audio data; valid
// if wBitsPerSample=0 (but rarely used).
WORD wReserved; // Zero if neither case above applies.
} Samples;
DWORD dwChannelMask; // Positions of the audio channels
GUID SubFormat; // Format identifier GUID
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE, *LPPWAVEFORMATEXTENSIBLE;
typedef const WAVEFORMATEXTENSIBLE* LPCWAVEFORMATEXTENSIBLE;
#endif
#if !defined(WAVE_FORMAT_EXTENSIBLE)
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
#endif
//#define DEBUG_AUDIO 1
QT_BEGIN_NAMESPACE
QWindowsAudioOutput::QWindowsAudioOutput(const QByteArray &device)
{
bytesAvailable = 0;
buffer_size = 0;
period_size = 0;
m_device = device;
totalTimeValue = 0;
intervalTime = 1000;
audioBuffer = 0;
errorState = QAudio::NoError;
deviceState = QAudio::StoppedState;
audioSource = 0;
pullMode = true;
finished = false;
volumeCache = (qreal)1.;
}
QWindowsAudioOutput::~QWindowsAudioOutput()
{
mutex.lock();
finished = true;
mutex.unlock();
close();
}
void CALLBACK QWindowsAudioOutput::waveOutProc( HWAVEOUT hWaveOut, UINT uMsg,
DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 )
{
Q_UNUSED(dwParam1)
Q_UNUSED(dwParam2)
Q_UNUSED(hWaveOut)
QWindowsAudioOutput* qAudio;
qAudio = (QWindowsAudioOutput*)(dwInstance);
if(!qAudio)
return;
QMutexLocker(&qAudio->mutex);
switch(uMsg) {
case WOM_OPEN:
qAudio->feedback();
break;
case WOM_CLOSE:
return;
case WOM_DONE:
if(qAudio->finished || qAudio->buffer_size == 0 || qAudio->period_size == 0) {
return;
}
qAudio->waveFreeBlockCount++;
if(qAudio->waveFreeBlockCount >= qAudio->buffer_size/qAudio->period_size)
qAudio->waveFreeBlockCount = qAudio->buffer_size/qAudio->period_size;
qAudio->feedback();
break;
default:
return;
}
}
WAVEHDR* QWindowsAudioOutput::allocateBlocks(int size, int count)
{
int i;
unsigned char* buffer;
WAVEHDR* blocks;
DWORD totalBufferSize = (size + sizeof(WAVEHDR))*count;
if((buffer=(unsigned char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
totalBufferSize)) == 0) {
qWarning("QAudioOutput: Memory allocation error");
return 0;
}
blocks = (WAVEHDR*)buffer;
buffer += sizeof(WAVEHDR)*count;
for(i = 0; i < count; i++) {
blocks[i].dwBufferLength = size;
blocks[i].lpData = (LPSTR)buffer;
buffer += size;
}
return blocks;
}
void QWindowsAudioOutput::freeBlocks(WAVEHDR* blockArray)
{
WAVEHDR* blocks = blockArray;
int count = buffer_size/period_size;
for(int i = 0; i < count; i++) {
waveOutUnprepareHeader(hWaveOut,blocks, sizeof(WAVEHDR));
blocks++;
}
HeapFree(GetProcessHeap(), 0, blockArray);
}
QAudioFormat QWindowsAudioOutput::format() const
{
return settings;
}
void QWindowsAudioOutput::setFormat(const QAudioFormat& fmt)
{
if (deviceState == QAudio::StoppedState)
settings = fmt;
}
void QWindowsAudioOutput::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* QWindowsAudioOutput::start()
{
if(deviceState != QAudio::StoppedState)
close();
if(!pullMode && audioSource)
delete audioSource;
pullMode = false;
audioSource = new OutputPrivate(this);
audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
deviceState = QAudio::IdleState;
if(!open())
return 0;
emit stateChanged(deviceState);
return audioSource;
}
void QWindowsAudioOutput::stop()
{
if(deviceState == QAudio::StoppedState)
return;
close();
if(!pullMode && audioSource) {
delete audioSource;
audioSource = 0;
}
emit stateChanged(deviceState);
}
bool QWindowsAudioOutput::open()
{
#ifdef DEBUG_AUDIO
QTime now(QTime::currentTime());
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
#endif
period_size = 0;
if (!settings.isValid()) {
qWarning("QAudioOutput: open error, invalid format.");
} else if (settings.channelCount() <= 0) {
qWarning("QAudioOutput: open error, invalid number of channels (%d).",
settings.channelCount());
} else if (settings.sampleSize() <= 0) {
qWarning("QAudioOutput: open error, invalid sample size (%d).",
settings.sampleSize());
} else if (settings.sampleRate() < 8000 || settings.sampleRate() > 96000) {
qWarning("QAudioOutput: open error, sample rate out of range (%d).", settings.sampleRate());
} else if (buffer_size == 0) {
// Default buffer size, 200ms, default period size is 40ms
buffer_size
= (settings.sampleRate()
* settings.channelCount()
* settings.sampleSize()
+ 39) / 40;
period_size = buffer_size / 5;
} else {
period_size = buffer_size / 5;
}
if (period_size == 0) {
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
return false;
}
waveBlocks = allocateBlocks(period_size, buffer_size/period_size);
mutex.lock();
waveFreeBlockCount = buffer_size/period_size;
mutex.unlock();
waveCurrentBlock = 0;
if(audioBuffer == 0)
audioBuffer = new char[buffer_size];
timeStamp.restart();
elapsedTimeOffset = 0;
wfx.nSamplesPerSec = settings.sampleRate();
wfx.wBitsPerSample = settings.sampleSize();
wfx.nChannels = settings.channelCount();
wfx.cbSize = 0;
bool surround = false;
if (settings.channelCount() > 2)
surround = true;
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
QDataStream ds(&m_device, QIODevice::ReadOnly);
quint32 deviceId;
ds >> deviceId;
if (!surround) {
if (waveOutOpen(&hWaveOut, UINT_PTR(deviceId), &wfx,
(DWORD_PTR)&waveOutProc,
(DWORD_PTR) this,
CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
qWarning("QAudioOutput: open error");
return false;
}
} else {
WAVEFORMATEXTENSIBLE wfex;
wfex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfex.Format.nChannels = settings.channelCount();
wfex.Format.wBitsPerSample = settings.sampleSize();
wfex.Format.nSamplesPerSec = settings.sampleRate();
wfex.Format.nBlockAlign = wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*wfex.Format.nBlockAlign;
wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
static const GUID _KSDATAFORMAT_SUBTYPE_PCM = {
0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
wfex.SubFormat=_KSDATAFORMAT_SUBTYPE_PCM;
wfex.Format.cbSize=22;
wfex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
if (settings.channelCount() >= 4)
wfex.dwChannelMask |= SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
if (settings.channelCount() >= 6)
wfex.dwChannelMask |= SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY;
if (settings.channelCount() == 8)
wfex.dwChannelMask |= SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
if (waveOutOpen(&hWaveOut, UINT_PTR(deviceId), &wfex.Format,
(DWORD_PTR)&waveOutProc,
(DWORD_PTR) this,
CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
qWarning("QAudioOutput: open error");
return false;
}
}
totalTimeValue = 0;
timeStampOpened.restart();
elapsedTimeOffset = 0;
setVolume(volumeCache);
errorState = QAudio::NoError;
if(pullMode) {
deviceState = QAudio::ActiveState;
QTimer::singleShot(10, this, SLOT(feedback()));
} else
deviceState = QAudio::IdleState;
return true;
}
void QWindowsAudioOutput::close()
{
if(deviceState == QAudio::StoppedState)
return;
deviceState = QAudio::StoppedState;
errorState = QAudio::NoError;
int delay = (buffer_size-bytesFree())*1000/(settings.sampleRate()
*settings.channelCount()*(settings.sampleSize()/8));
waveOutReset(hWaveOut);
Sleep(delay+10);
freeBlocks(waveBlocks);
waveOutClose(hWaveOut);
delete [] audioBuffer;
audioBuffer = 0;
buffer_size = 0;
}
int QWindowsAudioOutput::bytesFree() const
{
int buf;
buf = waveFreeBlockCount*period_size;
return buf;
}
int QWindowsAudioOutput::periodSize() const
{
return period_size;
}
void QWindowsAudioOutput::setBufferSize(int value)
{
if(deviceState == QAudio::StoppedState)
buffer_size = value;
}
int QWindowsAudioOutput::bufferSize() const
{
return buffer_size;
}
void QWindowsAudioOutput::setNotifyInterval(int ms)
{
intervalTime = qMax(0, ms);
}
int QWindowsAudioOutput::notifyInterval() const
{
return intervalTime;
}
qint64 QWindowsAudioOutput::processedUSecs() const
{
if (deviceState == QAudio::StoppedState)
return 0;
qint64 result = qint64(1000000) * totalTimeValue /
(settings.channelCount()*(settings.sampleSize()/8)) /
settings.sampleRate();
return result;
}
qint64 QWindowsAudioOutput::write( const char *data, qint64 len )
{
// Write out some audio data
if (deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
return 0;
char* p = (char*)data;
int l = (int)len;
QByteArray reverse;
if (settings.byteOrder() == QAudioFormat::BigEndian) {
switch (settings.sampleSize()) {
case 8:
// No need to convert
break;
case 16:
reverse.resize(l);
for (qint64 i = 0; i < (l >> 1); i++)
*((qint16*)reverse.data() + i) = qFromBigEndian(*((qint16*)data + i));
p = reverse.data();
break;
case 32:
reverse.resize(l);
for (qint64 i = 0; i < (l >> 2); i++)
*((qint32*)reverse.data() + i) = qFromBigEndian(*((qint32*)data + i));
p = reverse.data();
break;
}
}
WAVEHDR* current;
int remain;
current = &waveBlocks[waveCurrentBlock];
while(l > 0) {
mutex.lock();
if(waveFreeBlockCount==0) {
mutex.unlock();
break;
}
mutex.unlock();
if(current->dwFlags & WHDR_PREPARED)
waveOutUnprepareHeader(hWaveOut, current, sizeof(WAVEHDR));
if(l < period_size)
remain = l;
else
remain = period_size;
memcpy(current->lpData, p, remain);
l -= remain;
p += remain;
current->dwBufferLength = remain;
waveOutPrepareHeader(hWaveOut, current, sizeof(WAVEHDR));
waveOutWrite(hWaveOut, current, sizeof(WAVEHDR));
mutex.lock();
waveFreeBlockCount--;
#ifdef DEBUG_AUDIO
qDebug("write out l=%d, waveFreeBlockCount=%d",
current->dwBufferLength,waveFreeBlockCount);
#endif
mutex.unlock();
totalTimeValue += current->dwBufferLength;
waveCurrentBlock++;
waveCurrentBlock %= buffer_size/period_size;
current = &waveBlocks[waveCurrentBlock];
current->dwUser = 0;
errorState = QAudio::NoError;
if (deviceState != QAudio::ActiveState) {
deviceState = QAudio::ActiveState;
emit stateChanged(deviceState);
}
}
return (len-l);
}
void QWindowsAudioOutput::resume()
{
if(deviceState == QAudio::SuspendedState) {
deviceState = QAudio::ActiveState;
errorState = QAudio::NoError;
waveOutRestart(hWaveOut);
QTimer::singleShot(10, this, SLOT(feedback()));
emit stateChanged(deviceState);
}
}
void QWindowsAudioOutput::suspend()
{
if(deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState) {
int delay = (buffer_size-bytesFree())*1000/(settings.sampleRate()
*settings.channelCount()*(settings.sampleSize()/8));
waveOutPause(hWaveOut);
Sleep(delay+10);
deviceState = QAudio::SuspendedState;
errorState = QAudio::NoError;
emit stateChanged(deviceState);
}
}
void QWindowsAudioOutput::feedback()
{
#ifdef DEBUG_AUDIO
QTime now(QTime::currentTime());
qDebug()<<now.second()<<"s "<<now.msec()<<"ms :feedback()";
#endif
bytesAvailable = bytesFree();
if(!(deviceState==QAudio::StoppedState||deviceState==QAudio::SuspendedState)) {
if(bytesAvailable >= period_size)
QMetaObject::invokeMethod(this, "deviceReady", Qt::QueuedConnection);
}
}
bool QWindowsAudioOutput::deviceReady()
{
if(deviceState == QAudio::StoppedState || deviceState == QAudio::SuspendedState)
return false;
if(pullMode) {
int chunks = bytesAvailable/period_size;
#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 ="<<chunks*period_size;
#endif
bool startup = false;
if(totalTimeValue == 0)
startup = true;
bool full=false;
mutex.lock();
if (waveFreeBlockCount==0) full = true;
mutex.unlock();
if (full) {
#ifdef DEBUG_AUDIO
qDebug() << "Skipping data as unable to write";
#endif
if ((timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
emit notify();
elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
timeStamp.restart();
}
return true;
}
if(startup)
waveOutPause(hWaveOut);
int input = period_size*chunks;
int l = audioSource->read(audioBuffer,input);
if(l > 0) {
int out= write(audioBuffer,l);
if(out > 0) {
if (deviceState != QAudio::ActiveState) {
deviceState = QAudio::ActiveState;
emit stateChanged(deviceState);
}
}
if ( out < l) {
// Didn't write all data
audioSource->seek(audioSource->pos()-(l-out));
}
if (startup)
waveOutRestart(hWaveOut);
} else if(l == 0) {
bytesAvailable = bytesFree();
int check = 0;
mutex.lock();
check = waveFreeBlockCount;
mutex.unlock();
if(check == buffer_size/period_size) {
if (deviceState != QAudio::IdleState) {
errorState = QAudio::UnderrunError;
deviceState = QAudio::IdleState;
emit stateChanged(deviceState);
}
}
} else if(l < 0) {
bytesAvailable = bytesFree();
if (errorState != QAudio::IOError)
errorState = QAudio::IOError;
}
} else {
int buffered;
mutex.lock();
buffered = waveFreeBlockCount;
mutex.unlock();
if (buffered >= buffer_size/period_size && deviceState == QAudio::ActiveState) {
if (deviceState != QAudio::IdleState) {
errorState = QAudio::UnderrunError;
deviceState = QAudio::IdleState;
emit stateChanged(deviceState);
}
}
}
if(deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
return true;
if(intervalTime && (timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
emit notify();
elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
timeStamp.restart();
}
return true;
}
qint64 QWindowsAudioOutput::elapsedUSecs() const
{
if (deviceState == QAudio::StoppedState)
return 0;
return timeStampOpened.elapsed()*1000;
}
QAudio::Error QWindowsAudioOutput::error() const
{
return errorState;
}
QAudio::State QWindowsAudioOutput::state() const
{
return deviceState;
}
void QWindowsAudioOutput::setVolume(qreal v)
{
const qreal normalizedVolume = qBound(qreal(0.0), v, qreal(1.0));
if (deviceState != QAudio::ActiveState) {
volumeCache = normalizedVolume;
return;
}
const quint16 scaled = normalizedVolume * 0xFFFF;
DWORD vol = MAKELONG(scaled, scaled);
MMRESULT res = waveOutSetVolume(hWaveOut, vol);
if (res == MMSYSERR_NOERROR)
volumeCache = normalizedVolume;
}
qreal QWindowsAudioOutput::volume() const
{
return volumeCache;
}
void QWindowsAudioOutput::reset()
{
close();
}
OutputPrivate::OutputPrivate(QWindowsAudioOutput* audio)
{
audioDevice = qobject_cast<QWindowsAudioOutput*>(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)) {
qint64 l = len;
while(written < l) {
int chunk = audioDevice->write(data+written,(l-written));
if(chunk <= 0)
retry++;
else
written+=chunk;
if(retry > 10)
return written;
}
audioDevice->deviceState = QAudio::ActiveState;
}
return written;
}
QT_END_NAMESPACE
#include "moc_qwindowsaudiooutput.cpp"

View File

@@ -0,0 +1,171 @@
/****************************************************************************
**
** 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 QWINDOWSAUDIOOUTPUT_H
#define QWINDOWSAUDIOOUTPUT_H
#include <QtCore/qt_windows.h>
#include <mmsystem.h>
#include <QtCore/qdebug.h>
#include <QtCore/qtimer.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qmutex.h>
#include <QtMultimedia/qaudio.h>
#include <QtMultimedia/qaudiodeviceinfo.h>
#include <QtMultimedia/qaudiosystem.h>
// For compat with 4.6
#if !defined(QT_WIN_CALLBACK)
# if defined(Q_CC_MINGW)
# define QT_WIN_CALLBACK CALLBACK __attribute__ ((force_align_arg_pointer))
# else
# define QT_WIN_CALLBACK CALLBACK
# endif
#endif
QT_BEGIN_NAMESPACE
class QWindowsAudioOutput : public QAbstractAudioOutput
{
Q_OBJECT
public:
QWindowsAudioOutput(const QByteArray &device);
~QWindowsAudioOutput();
qint64 write( const char *data, qint64 len );
void setFormat(const QAudioFormat& fmt);
QAudioFormat format() const;
QIODevice* start();
void start(QIODevice* device);
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 setVolume(qreal);
qreal volume() const;
QIODevice* audioSource;
QAudioFormat settings;
QAudio::Error errorState;
QAudio::State deviceState;
private slots:
void feedback();
bool deviceReady();
private:
QByteArray m_device;
bool resuming;
int bytesAvailable;
QTime timeStamp;
qint64 elapsedTimeOffset;
QTime timeStampOpened;
qint32 buffer_size;
qint32 period_size;
qint64 totalTimeValue;
bool pullMode;
int intervalTime;
qreal volumeCache;
static void QT_WIN_CALLBACK waveOutProc( HWAVEOUT hWaveOut, UINT uMsg,
DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 );
QMutex mutex;
WAVEHDR* allocateBlocks(int size, int count);
void freeBlocks(WAVEHDR* blockArray);
bool open();
void close();
WAVEFORMATEX wfx;
HWAVEOUT hWaveOut;
MMRESULT result;
WAVEHDR header;
WAVEHDR* waveBlocks;
volatile bool finished;
volatile int waveFreeBlockCount;
int waveCurrentBlock;
char* audioBuffer;
};
class OutputPrivate : public QIODevice
{
Q_OBJECT
public:
OutputPrivate(QWindowsAudioOutput* audio);
~OutputPrivate();
qint64 readData( char* data, qint64 len);
qint64 writeData(const char* data, qint64 len);
private:
QWindowsAudioOutput *audioDevice;
};
QT_END_NAMESPACE
#endif // QWINDOWSAUDIOOUTPUT_H

View 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 "qwindowsaudioplugin.h"
#include "qwindowsaudiodeviceinfo.h"
#include "qwindowsaudioinput.h"
#include "qwindowsaudiooutput.h"
QT_BEGIN_NAMESPACE
QWindowsAudioPlugin::QWindowsAudioPlugin(QObject *parent)
: QAudioSystemPlugin(parent)
{
}
QList<QByteArray> QWindowsAudioPlugin::availableDevices(QAudio::Mode mode) const
{
return QWindowsAudioDeviceInfo::availableDevices(mode);
}
QAbstractAudioInput *QWindowsAudioPlugin::createInput(const QByteArray &device)
{
return new QWindowsAudioInput(device);
}
QAbstractAudioOutput *QWindowsAudioPlugin::createOutput(const QByteArray &device)
{
return new QWindowsAudioOutput(device);
}
QAbstractAudioDeviceInfo *QWindowsAudioPlugin::createDeviceInfo(const QByteArray &device, QAudio::Mode mode)
{
return new QWindowsAudioDeviceInfo(device, mode);
}
QT_END_NAMESPACE

View 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 QWINDOWSAUDIOPLUGIN_H
#define QWINDOWSAUDIOPLUGIN_H
#include <QtMultimedia/qaudiosystemplugin.h>
QT_BEGIN_NAMESPACE
class QWindowsAudioPlugin : public QAudioSystemPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.qt.audiosystemfactory/5.0" FILE "windowsaudio.json")
public:
QWindowsAudioPlugin(QObject *parent = 0);
~QWindowsAudioPlugin() {}
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 // QWINDOWSAUDIOPLUGIN_H

View File

@@ -0,0 +1,3 @@
{
"Keys": ["default"]
}

View File

@@ -0,0 +1,23 @@
TARGET = qtaudio_windows
QT += multimedia-private
PLUGIN_TYPE = audio
PLUGIN_CLASS_NAME = QWindowsAudioPlugin
load(qt_plugin)
LIBS += -lwinmm -lstrmiids -lole32 -loleaut32
HEADERS += \
qwindowsaudioplugin.h \
qwindowsaudiodeviceinfo.h \
qwindowsaudioinput.h \
qwindowsaudiooutput.h
SOURCES += \
qwindowsaudioplugin.cpp \
qwindowsaudiodeviceinfo.cpp \
qwindowsaudioinput.cpp \
qwindowsaudiooutput.cpp
OTHER_FILES += \
windowsaudio.json