blob: 70e7fbce5d26ecd6a04f23f26bc603f46f9b326f [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
//
// 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 <QtCore/QDataStream>
#include <mmsystem.h>
#include "qwindowsaudiodeviceinfo.h"
#include "qwindowsaudioutils.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()
{
return QStringList() << QStringLiteral("audio/pcm");
}
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()
{
return QList<QAudioFormat::Endian>() << QAudioFormat::LittleEndian;
}
QList<QAudioFormat::SampleType> QWindowsAudioDeviceInfo::supportedSampleTypes()
{
updateLists();
return typez;
}
bool QWindowsAudioDeviceInfo::open()
{
return true;
}
void QWindowsAudioDeviceInfo::close()
{
}
bool QWindowsAudioDeviceInfo::testSettings(const QAudioFormat& format) const
{
WAVEFORMATEXTENSIBLE wfx;
if (qt_convertFormat(format, &wfx)) {
// query only, do not open device
if (mode == QAudio::AudioOutput) {
return (waveOutOpen(NULL, UINT_PTR(devId), &wfx.Format, 0, 0,
WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR);
} else { // AudioInput
return (waveInOpen(NULL, UINT_PTR(devId), &wfx.Format, 0, 0,
WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR);
}
}
return false;
}
void QWindowsAudioDeviceInfo::updateLists()
{
if (!sizez.isEmpty())
return;
DWORD fmt = 0;
if(mode == QAudio::AudioOutput) {
WAVEOUTCAPS woc;
if (waveOutGetDevCaps(devId, &woc, sizeof(WAVEOUTCAPS)) == MMSYSERR_NOERROR)
fmt = woc.dwFormats;
} else {
WAVEINCAPS woc;
if (waveInGetDevCaps(devId, &woc, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR)
fmt = woc.dwFormats;
}
sizez.clear();
sampleRatez.clear();
channelz.clear();
typez.clear();
if (fmt) {
// Check sample size
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);
}
// Check sample rate
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);
}
// Check channel count
if (fmt & WAVE_FORMAT_1M08
|| fmt & WAVE_FORMAT_1M16
|| fmt & WAVE_FORMAT_2M08
|| fmt & WAVE_FORMAT_2M16
|| fmt & WAVE_FORMAT_4M08
|| fmt & WAVE_FORMAT_4M16
|| fmt & WAVE_FORMAT_48M08
|| fmt & WAVE_FORMAT_48M16
|| fmt & WAVE_FORMAT_96M08
|| fmt & WAVE_FORMAT_96M16) {
channelz.append(1);
}
if (fmt & WAVE_FORMAT_1S08
|| fmt & WAVE_FORMAT_1S16
|| fmt & WAVE_FORMAT_2S08
|| fmt & WAVE_FORMAT_2S16
|| fmt & WAVE_FORMAT_4S08
|| fmt & WAVE_FORMAT_4S16
|| fmt & WAVE_FORMAT_48S08
|| fmt & WAVE_FORMAT_48S16
|| fmt & WAVE_FORMAT_96S08
|| fmt & WAVE_FORMAT_96S16) {
channelz.append(2);
}
typez.append(QAudioFormat::SignedInt);
typez.append(QAudioFormat::UnSignedInt);
// WAVEOUTCAPS and WAVEINCAPS contains information only for the previously tested parameters.
// WaveOut and WaveInt might actually support more formats, the only way to know is to try
// opening the device with it.
QAudioFormat testFormat;
testFormat.setCodec(QStringLiteral("audio/pcm"));
testFormat.setByteOrder(QAudioFormat::LittleEndian);
testFormat.setSampleType(QAudioFormat::SignedInt);
testFormat.setChannelCount(channelz.first());
testFormat.setSampleRate(sampleRatez.at(sampleRatez.size() / 2));
testFormat.setSampleSize(sizez.last());
const QAudioFormat defaultTestFormat(testFormat);
// Check if float samples are supported
testFormat.setSampleType(QAudioFormat::Float);
testFormat.setSampleSize(32);
if (testSettings(testFormat))
typez.append(QAudioFormat::Float);
// Check channel counts > 2
testFormat = defaultTestFormat;
for (int i = 3; i < 19; ++i) { // <mmreg.h> defines 18 different channels
testFormat.setChannelCount(i);
if (testSettings(testFormat))
channelz.append(i);
}
// Check more sample sizes
testFormat = defaultTestFormat;
const QList<int> testSampleSizes = QList<int>() << 24 << 32 << 48 << 64;
for (int s : testSampleSizes) {
testFormat.setSampleSize(s);
if (testSettings(testFormat))
sizez.append(s);
}
// Check more sample rates
testFormat = defaultTestFormat;
const QList<int> testSampleRates = QList<int>() << 8000 << 16000 << 32000 << 88200 << 192000;
for (int r : testSampleRates) {
testFormat.setSampleRate(r);
if (testSettings(testFormat))
sampleRatez.append(r);
}
std::sort(sampleRatez.begin(), sampleRatez.end());
}
}
QList<QByteArray> QWindowsAudioDeviceInfo::availableDevices(QAudio::Mode mode)
{
Q_UNUSED(mode)
QList<QByteArray> devices;
//enumerate device fullnames through directshow api
auto hrCoInit = CoInitialize(nullptr);
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();
}
pEnum->Release();
}
pDevEnum->Release();
}
if (SUCCEEDED(hrCoInit))
CoUninitialize();
return devices;
}
QByteArray QWindowsAudioDeviceInfo::defaultDevice(QAudio::Mode mode)
{
const QString &name = (mode == QAudio::AudioOutput) ? QStringLiteral("Default Output Device")
: QStringLiteral("Default Input Device");
QByteArray defaultDevice;
QDataStream ds(&defaultDevice, QIODevice::WriteOnly);
ds << quint32(WAVE_MAPPER) // device ID for default device
<< name;
return defaultDevice;
}
QT_END_NAMESPACE