Move win32 and Alsa audio backends into plugins.
Change-Id: I9835cf5ee97900569f26421a19543b485e933051 Reviewed-by: Yoann Lopes <yoann.lopes@digia.com>
This commit is contained in:
committed by
The Qt Project
parent
0ab81ef59f
commit
2d54da2d39
490
src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp
Normal file
490
src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp
Normal 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
|
||||
114
src/plugins/windowsaudio/qwindowsaudiodeviceinfo.h
Normal file
114
src/plugins/windowsaudio/qwindowsaudiodeviceinfo.h
Normal 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
|
||||
752
src/plugins/windowsaudio/qwindowsaudioinput.cpp
Normal file
752
src/plugins/windowsaudio/qwindowsaudioinput.cpp
Normal 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"
|
||||
179
src/plugins/windowsaudio/qwindowsaudioinput.h
Normal file
179
src/plugins/windowsaudio/qwindowsaudioinput.h
Normal 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
|
||||
762
src/plugins/windowsaudio/qwindowsaudiooutput.cpp
Normal file
762
src/plugins/windowsaudio/qwindowsaudiooutput.cpp
Normal 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"
|
||||
171
src/plugins/windowsaudio/qwindowsaudiooutput.h
Normal file
171
src/plugins/windowsaudio/qwindowsaudiooutput.h
Normal 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
|
||||
74
src/plugins/windowsaudio/qwindowsaudioplugin.cpp
Normal file
74
src/plugins/windowsaudio/qwindowsaudioplugin.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "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
|
||||
67
src/plugins/windowsaudio/qwindowsaudioplugin.h
Normal file
67
src/plugins/windowsaudio/qwindowsaudioplugin.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef 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
|
||||
3
src/plugins/windowsaudio/windowsaudio.json
Normal file
3
src/plugins/windowsaudio/windowsaudio.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Keys": ["default"]
|
||||
}
|
||||
23
src/plugins/windowsaudio/windowsaudio.pro
Normal file
23
src/plugins/windowsaudio/windowsaudio.pro
Normal 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
|
||||
Reference in New Issue
Block a user