blob: 6ccffc8b2933be23fd373eb373b4c7bac4eb7c92 [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 "qwindowsaudiooutput.h"
#include "qwindowsaudiodeviceinfo.h"
#include "qwindowsaudioutils.h"
#include <QtEndian>
#include <QtCore/QDataStream>
#include <QtCore/qtimer.h>
#include <private/qaudiohelpers_p.h>
//#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.0);
blocks_count = 5;
}
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 locker(&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->blocks_count)
qAudio->waveFreeBlockCount = qAudio->blocks_count;
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;
for (int i = 0; i < blocks_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 (!qt_convertFormat(settings, &wfx)) {
qWarning("QAudioOutput: open error, invalid format.");
} 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;
}
// Make even size of wave block to prevent crackling
// due to waveOutWrite() does not like odd buffer length
period_size &= ~1;
if (period_size == 0) {
errorState = QAudio::OpenError;
deviceState = QAudio::StoppedState;
emit stateChanged(deviceState);
return false;
}
const int periods = buffer_size / period_size;
bool ok = false;
static int wave_buffers = qEnvironmentVariableIntValue("QT_WAVE_BUFFERS", &ok);
if (wave_buffers < periods) {
if (ok)
qWarning("Number of WAVE buffers (QT_WAVE_BUFFERS=%d) cannot be less than %d.", wave_buffers, periods);
wave_buffers = periods;
}
blocks_count = wave_buffers;
waveBlocks = allocateBlocks(period_size, blocks_count);
mutex.lock();
waveFreeBlockCount = blocks_count;
mutex.unlock();
waveCurrentBlock = 0;
if(audioBuffer == 0)
audioBuffer = new char[blocks_count * period_size];
timeStamp.restart();
elapsedTimeOffset = 0;
QDataStream ds(&m_device, QIODevice::ReadOnly);
quint32 deviceId;
ds >> deviceId;
if (waveOutOpen(&hWaveOut, UINT_PTR(deviceId), &wfx.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;
errorState = QAudio::NoError;
if(pullMode) {
deviceState = QAudio::ActiveState;
QTimer::singleShot(10, this, SLOT(feedback()));
} else
deviceState = QAudio::IdleState;
return true;
}
void QWindowsAudioOutput::pauseAndSleep()
{
waveOutPause(hWaveOut);
int bitrate = settings.sampleRate() * settings.channelCount() * settings.sampleSize() / 8;
// Time of written data.
int delay = (buffer_size - bytesFree()) * 1000 / bitrate;
Sleep(delay + 10);
}
void QWindowsAudioOutput::close()
{
if(deviceState == QAudio::StoppedState)
return;
// Pause playback before reset to avoid uneeded crackling at the end.
pauseAndSleep();
deviceState = QAudio::StoppedState;
errorState = QAudio::NoError;
waveOutReset(hWaveOut);
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;
if (volumeCache < qreal(1.0))
QAudioHelperInternal::qMultiplySamples(volumeCache, settings, p, current->lpData, remain);
else
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 %= blocks_count;
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 = pullMode ? QAudio::ActiveState : QAudio::IdleState;
errorState = QAudio::NoError;
waveOutRestart(hWaveOut);
QTimer::singleShot(10, this, SLOT(feedback()));
emit stateChanged(deviceState);
}
}
void QWindowsAudioOutput::suspend()
{
if(deviceState == QAudio::ActiveState || deviceState == QAudio::IdleState) {
pauseAndSleep();
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 == blocks_count) {
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 >= blocks_count && 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() * qint64(1000);
}
QAudio::Error QWindowsAudioOutput::error() const
{
return errorState;
}
QAudio::State QWindowsAudioOutput::state() const
{
return deviceState;
}
void QWindowsAudioOutput::setVolume(qreal v)
{
if (qFuzzyCompare(volumeCache, v))
return;
volumeCache = qBound(qreal(0), v, qreal(1));
}
qreal QWindowsAudioOutput::volume() const
{
return volumeCache;
}
void QWindowsAudioOutput::reset()
{
stop();
}
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"