diff options
author | Christian Strømme <christian.stromme@digia.com> | 2013-11-23 00:14:15 +0100 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2014-01-30 18:15:36 +0100 |
commit | 2d54da2d39217e7b21ccafa9594513d554352a24 (patch) | |
tree | b9a620c05741fc81875c11fa351ea25713b1583c /src/plugins/windowsaudio | |
parent | 0ab81ef59f35d103ec8174834c4fc2a4dcced453 (diff) | |
download | qtmultimedia-2d54da2d39217e7b21ccafa9594513d554352a24.tar.gz |
Move win32 and Alsa audio backends into plugins.
Change-Id: I9835cf5ee97900569f26421a19543b485e933051
Reviewed-by: Yoann Lopes <yoann.lopes@digia.com>
Diffstat (limited to 'src/plugins/windowsaudio')
-rw-r--r-- | src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp | 490 | ||||
-rw-r--r-- | src/plugins/windowsaudio/qwindowsaudiodeviceinfo.h | 114 | ||||
-rw-r--r-- | src/plugins/windowsaudio/qwindowsaudioinput.cpp | 752 | ||||
-rw-r--r-- | src/plugins/windowsaudio/qwindowsaudioinput.h | 179 | ||||
-rw-r--r-- | src/plugins/windowsaudio/qwindowsaudiooutput.cpp | 762 | ||||
-rw-r--r-- | src/plugins/windowsaudio/qwindowsaudiooutput.h | 171 | ||||
-rw-r--r-- | src/plugins/windowsaudio/qwindowsaudioplugin.cpp | 74 | ||||
-rw-r--r-- | src/plugins/windowsaudio/qwindowsaudioplugin.h | 67 | ||||
-rw-r--r-- | src/plugins/windowsaudio/windowsaudio.json | 3 | ||||
-rw-r--r-- | src/plugins/windowsaudio/windowsaudio.pro | 23 |
10 files changed, 2635 insertions, 0 deletions
diff --git a/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp b/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp new file mode 100644 index 000000000..d37056a5f --- /dev/null +++ b/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp @@ -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 diff --git a/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.h b/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.h new file mode 100644 index 000000000..817b803f6 --- /dev/null +++ b/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.h @@ -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 diff --git a/src/plugins/windowsaudio/qwindowsaudioinput.cpp b/src/plugins/windowsaudio/qwindowsaudioinput.cpp new file mode 100644 index 000000000..26f0641bf --- /dev/null +++ b/src/plugins/windowsaudio/qwindowsaudioinput.cpp @@ -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" diff --git a/src/plugins/windowsaudio/qwindowsaudioinput.h b/src/plugins/windowsaudio/qwindowsaudioinput.h new file mode 100644 index 000000000..7498bca5d --- /dev/null +++ b/src/plugins/windowsaudio/qwindowsaudioinput.h @@ -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 diff --git a/src/plugins/windowsaudio/qwindowsaudiooutput.cpp b/src/plugins/windowsaudio/qwindowsaudiooutput.cpp new file mode 100644 index 000000000..1c8882eeb --- /dev/null +++ b/src/plugins/windowsaudio/qwindowsaudiooutput.cpp @@ -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" diff --git a/src/plugins/windowsaudio/qwindowsaudiooutput.h b/src/plugins/windowsaudio/qwindowsaudiooutput.h new file mode 100644 index 000000000..77f23f701 --- /dev/null +++ b/src/plugins/windowsaudio/qwindowsaudiooutput.h @@ -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 diff --git a/src/plugins/windowsaudio/qwindowsaudioplugin.cpp b/src/plugins/windowsaudio/qwindowsaudioplugin.cpp new file mode 100644 index 000000000..79aabdbf9 --- /dev/null +++ b/src/plugins/windowsaudio/qwindowsaudioplugin.cpp @@ -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 diff --git a/src/plugins/windowsaudio/qwindowsaudioplugin.h b/src/plugins/windowsaudio/qwindowsaudioplugin.h new file mode 100644 index 000000000..3305f0553 --- /dev/null +++ b/src/plugins/windowsaudio/qwindowsaudioplugin.h @@ -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 diff --git a/src/plugins/windowsaudio/windowsaudio.json b/src/plugins/windowsaudio/windowsaudio.json new file mode 100644 index 000000000..a31d52107 --- /dev/null +++ b/src/plugins/windowsaudio/windowsaudio.json @@ -0,0 +1,3 @@ +{ + "Keys": ["default"] +} diff --git a/src/plugins/windowsaudio/windowsaudio.pro b/src/plugins/windowsaudio/windowsaudio.pro new file mode 100644 index 000000000..a1a327953 --- /dev/null +++ b/src/plugins/windowsaudio/windowsaudio.pro @@ -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 |