src/multimedia/audio/qaudiooutput_win32_p.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtMultimedia module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 //
       
    43 //  W A R N I N G
       
    44 //  -------------
       
    45 //
       
    46 // This file is not part of the Qt API.  It exists for the convenience
       
    47 // of other Qt classes.  This header file may change from version to
       
    48 // version without notice, or even be removed.
       
    49 //
       
    50 // We mean it.
       
    51 //
       
    52 
       
    53 #include "qaudiooutput_win32_p.h"
       
    54 
       
    55 //#define DEBUG_AUDIO 1
       
    56 
       
    57 QT_BEGIN_NAMESPACE
       
    58 
       
    59 static CRITICAL_SECTION waveOutCriticalSection;
       
    60 
       
    61 static const int minimumIntervalTime = 50;
       
    62 
       
    63 QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray &device, const QAudioFormat& audioFormat):
       
    64     settings(audioFormat)
       
    65 {
       
    66     bytesAvailable = 0;
       
    67     buffer_size = 0;
       
    68     period_size = 0;
       
    69     m_device = device;
       
    70     totalTimeValue = 0;
       
    71     intervalTime = 1000;
       
    72     audioBuffer = 0;
       
    73     errorState = QAudio::NoError;
       
    74     deviceState = QAudio::StopState;
       
    75     audioSource = 0;
       
    76     pullMode = true;
       
    77     finished = false;
       
    78     InitializeCriticalSection(&waveOutCriticalSection);
       
    79 }
       
    80 
       
    81 QAudioOutputPrivate::~QAudioOutputPrivate()
       
    82 {
       
    83     EnterCriticalSection(&waveOutCriticalSection);
       
    84     finished = true;
       
    85     LeaveCriticalSection(&waveOutCriticalSection);
       
    86 
       
    87     close();
       
    88     DeleteCriticalSection(&waveOutCriticalSection);
       
    89 }
       
    90 
       
    91 void CALLBACK QAudioOutputPrivate::waveOutProc( HWAVEOUT hWaveOut, UINT uMsg,
       
    92         DWORD dwInstance, DWORD dwParam1, DWORD dwParam2 )
       
    93 {
       
    94     Q_UNUSED(dwParam1)
       
    95     Q_UNUSED(dwParam2)
       
    96     Q_UNUSED(hWaveOut)
       
    97 
       
    98     QAudioOutputPrivate* qAudio;
       
    99     qAudio = (QAudioOutputPrivate*)(dwInstance);
       
   100     if(!qAudio)
       
   101         return;
       
   102 
       
   103     switch(uMsg) {
       
   104         case WOM_OPEN:
       
   105             qAudio->feedback();
       
   106             break;
       
   107         case WOM_CLOSE:
       
   108             return;
       
   109         case WOM_DONE:
       
   110             EnterCriticalSection(&waveOutCriticalSection);
       
   111             if(qAudio->finished || qAudio->buffer_size == 0 || qAudio->period_size == 0) {
       
   112                 LeaveCriticalSection(&waveOutCriticalSection);
       
   113                 return;
       
   114 	    }
       
   115             qAudio->waveFreeBlockCount++;
       
   116             if(qAudio->waveFreeBlockCount >= qAudio->buffer_size/qAudio->period_size)
       
   117                 qAudio->waveFreeBlockCount = qAudio->buffer_size/qAudio->period_size;
       
   118             qAudio->feedback();
       
   119             LeaveCriticalSection(&waveOutCriticalSection);
       
   120             break;
       
   121         default:
       
   122             return;
       
   123     }
       
   124 }
       
   125 
       
   126 WAVEHDR* QAudioOutputPrivate::allocateBlocks(int size, int count)
       
   127 {
       
   128     int i;
       
   129     unsigned char* buffer;
       
   130     WAVEHDR* blocks;
       
   131     DWORD totalBufferSize = (size + sizeof(WAVEHDR))*count;
       
   132 
       
   133     if((buffer=(unsigned char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
       
   134                     totalBufferSize)) == 0) {
       
   135         qWarning("QAudioOutput: Memory allocation error");
       
   136         return 0;
       
   137     }
       
   138     blocks = (WAVEHDR*)buffer;
       
   139     buffer += sizeof(WAVEHDR)*count;
       
   140     for(i = 0; i < count; i++) {
       
   141         blocks[i].dwBufferLength = size;
       
   142         blocks[i].lpData = (LPSTR)buffer;
       
   143         buffer += size;
       
   144     }
       
   145     return blocks;
       
   146 }
       
   147 
       
   148 void QAudioOutputPrivate::freeBlocks(WAVEHDR* blockArray)
       
   149 {
       
   150     HeapFree(GetProcessHeap(), 0, blockArray);
       
   151 }
       
   152 
       
   153 QAudioFormat QAudioOutputPrivate::format() const
       
   154 {
       
   155     return settings;
       
   156 }
       
   157 
       
   158 QIODevice* QAudioOutputPrivate::start(QIODevice* device)
       
   159 {
       
   160     if(deviceState != QAudio::StopState)
       
   161         close();
       
   162 
       
   163     if(!pullMode && audioSource) {
       
   164         delete audioSource;
       
   165     }
       
   166 
       
   167     if(device) {
       
   168         //set to pull mode
       
   169         pullMode = true;
       
   170         audioSource = device;
       
   171         deviceState = QAudio::ActiveState;
       
   172     } else {
       
   173         //set to push mode
       
   174         pullMode = false;
       
   175         audioSource = new OutputPrivate(this);
       
   176         audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
       
   177         deviceState = QAudio::IdleState;
       
   178     }
       
   179 
       
   180     if( !open() )
       
   181         return 0;
       
   182 
       
   183     emit stateChanged(deviceState);
       
   184 
       
   185     return audioSource;
       
   186 }
       
   187 
       
   188 void QAudioOutputPrivate::stop()
       
   189 {
       
   190     if(deviceState == QAudio::StopState)
       
   191         return;
       
   192     close();
       
   193     if(!pullMode && audioSource) {
       
   194         delete audioSource;
       
   195         audioSource = 0;
       
   196     }
       
   197     emit stateChanged(deviceState);
       
   198 }
       
   199 
       
   200 bool QAudioOutputPrivate::open()
       
   201 {
       
   202 #ifdef DEBUG_AUDIO
       
   203     QTime now(QTime::currentTime());
       
   204     qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
       
   205 #endif
       
   206     if(buffer_size == 0) {
       
   207         // Default buffer size, 200ms, default period size is 40ms
       
   208         buffer_size = settings.frequency()*settings.channels()*(settings.sampleSize()/8)*0.2;
       
   209 	period_size = buffer_size/5;
       
   210     } else {
       
   211         period_size = buffer_size/5;
       
   212     }
       
   213     waveBlocks = allocateBlocks(period_size, buffer_size/period_size);
       
   214 
       
   215     EnterCriticalSection(&waveOutCriticalSection);
       
   216     waveFreeBlockCount = buffer_size/period_size;
       
   217     LeaveCriticalSection(&waveOutCriticalSection);
       
   218 
       
   219     waveCurrentBlock = 0;
       
   220 
       
   221     if(audioBuffer == 0)
       
   222         audioBuffer = new char[buffer_size];
       
   223 
       
   224     timeStamp.restart();
       
   225     elapsedTimeOffset = 0;
       
   226 
       
   227     wfx.nSamplesPerSec = settings.frequency();
       
   228     wfx.wBitsPerSample = settings.sampleSize();
       
   229     wfx.nChannels = settings.channels();
       
   230     wfx.cbSize = 0;
       
   231 
       
   232     wfx.wFormatTag = WAVE_FORMAT_PCM;
       
   233     wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
       
   234     wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
       
   235 
       
   236     UINT_PTR devId = WAVE_MAPPER;
       
   237 
       
   238     WAVEOUTCAPS woc;
       
   239     unsigned long iNumDevs,ii;
       
   240     iNumDevs = waveOutGetNumDevs();
       
   241     for(ii=0;ii<iNumDevs;ii++) {
       
   242         if(waveOutGetDevCaps(ii, &woc, sizeof(WAVEOUTCAPS))
       
   243 	    == MMSYSERR_NOERROR) {
       
   244 	    QString tmp;
       
   245 	    tmp = QString::fromUtf16((const unsigned short*)woc.szPname);
       
   246             if(tmp.compare(QLatin1String(m_device)) == 0) {
       
   247 	        devId = ii;
       
   248 		break;
       
   249 	    }
       
   250 	}
       
   251     }
       
   252 
       
   253     if(waveOutOpen(&hWaveOut, devId, &wfx,
       
   254                 (DWORD_PTR)&waveOutProc,
       
   255                 (DWORD_PTR) this,
       
   256                 CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
       
   257         errorState = QAudio::OpenError;
       
   258         deviceState = QAudio::StopState;
       
   259         emit stateChanged(deviceState);
       
   260         qWarning("QAudioOutput: open error");
       
   261         return false;
       
   262     }
       
   263 
       
   264     totalTimeValue = 0;
       
   265     timeStampOpened.restart();
       
   266     elapsedTimeOffset = 0;
       
   267 
       
   268     errorState = QAudio::NoError;
       
   269     if(pullMode) {
       
   270         deviceState = QAudio::ActiveState;
       
   271         QTimer::singleShot(10, this, SLOT(feedback()));
       
   272     } else
       
   273         deviceState = QAudio::IdleState;
       
   274 
       
   275     return true;
       
   276 }
       
   277 
       
   278 void QAudioOutputPrivate::close()
       
   279 {
       
   280     if(deviceState == QAudio::StopState)
       
   281         return;
       
   282 
       
   283     deviceState = QAudio::StopState;
       
   284     int delay = (buffer_size-bytesFree())*1000/(settings.frequency()
       
   285                   *settings.channels()*(settings.sampleSize()/8));
       
   286     waveOutReset(hWaveOut);
       
   287     Sleep(delay+10);
       
   288 
       
   289     freeBlocks(waveBlocks);
       
   290     waveOutClose(hWaveOut);
       
   291     delete [] audioBuffer;
       
   292     audioBuffer = 0;
       
   293     buffer_size = 0;
       
   294 }
       
   295 
       
   296 int QAudioOutputPrivate::bytesFree() const
       
   297 {
       
   298     int buf;
       
   299     buf = waveFreeBlockCount*period_size;
       
   300 
       
   301     return buf;
       
   302 }
       
   303 
       
   304 int QAudioOutputPrivate::periodSize() const
       
   305 {
       
   306     return period_size;
       
   307 }
       
   308 
       
   309 void QAudioOutputPrivate::setBufferSize(int value)
       
   310 {
       
   311     if(deviceState == QAudio::StopState)
       
   312         buffer_size = value;
       
   313 }
       
   314 
       
   315 int QAudioOutputPrivate::bufferSize() const
       
   316 {
       
   317     return buffer_size;
       
   318 }
       
   319 
       
   320 void QAudioOutputPrivate::setNotifyInterval(int ms)
       
   321 {
       
   322     if(ms >= minimumIntervalTime)
       
   323         intervalTime = ms;
       
   324     else
       
   325         intervalTime = minimumIntervalTime;
       
   326 }
       
   327 
       
   328 int QAudioOutputPrivate::notifyInterval() const
       
   329 {
       
   330     return intervalTime;
       
   331 }
       
   332 
       
   333 qint64 QAudioOutputPrivate::totalTime() const
       
   334 {
       
   335     return totalTimeValue;
       
   336 }
       
   337 
       
   338 qint64 QAudioOutputPrivate::write( const char *data, qint64 len )
       
   339 {
       
   340     // Write out some audio data
       
   341 
       
   342     char* p = (char*)data;
       
   343     int l = (int)len;
       
   344 
       
   345     WAVEHDR* current;
       
   346     int remain;
       
   347     current = &waveBlocks[waveCurrentBlock];
       
   348     while(l > 0) {
       
   349         EnterCriticalSection(&waveOutCriticalSection);
       
   350         if(waveFreeBlockCount==0) {
       
   351             LeaveCriticalSection(&waveOutCriticalSection);
       
   352             break;
       
   353         }
       
   354         LeaveCriticalSection(&waveOutCriticalSection);
       
   355 
       
   356         if(current->dwFlags & WHDR_PREPARED)
       
   357             waveOutUnprepareHeader(hWaveOut, current, sizeof(WAVEHDR));
       
   358 
       
   359         if(l < period_size)
       
   360             remain = l;
       
   361         else
       
   362             remain = period_size;
       
   363         memcpy(current->lpData, p, remain);
       
   364 
       
   365         l -= remain;
       
   366         p += remain;
       
   367         current->dwBufferLength = remain;
       
   368         waveOutPrepareHeader(hWaveOut, current, sizeof(WAVEHDR));
       
   369         waveOutWrite(hWaveOut, current, sizeof(WAVEHDR));
       
   370 
       
   371         EnterCriticalSection(&waveOutCriticalSection);
       
   372         waveFreeBlockCount--;
       
   373         LeaveCriticalSection(&waveOutCriticalSection);
       
   374 #ifdef DEBUG_AUDIO
       
   375         EnterCriticalSection(&waveOutCriticalSection);
       
   376         qDebug("write out l=%d, waveFreeBlockCount=%d",
       
   377                 current->dwBufferLength,waveFreeBlockCount);
       
   378         LeaveCriticalSection(&waveOutCriticalSection);
       
   379 #endif
       
   380         totalTimeValue += current->dwBufferLength
       
   381             /(settings.channels()*(settings.sampleSize()/8))
       
   382             *1000000/settings.frequency();;
       
   383         waveCurrentBlock++;
       
   384         waveCurrentBlock %= buffer_size/period_size;
       
   385         current = &waveBlocks[waveCurrentBlock];
       
   386         current->dwUser = 0;
       
   387     }
       
   388     return (len-l);
       
   389 }
       
   390 
       
   391 void QAudioOutputPrivate::resume()
       
   392 {
       
   393     if(deviceState == QAudio::SuspendState) {
       
   394         deviceState = QAudio::ActiveState;
       
   395         errorState = QAudio::NoError;
       
   396         waveOutRestart(hWaveOut);
       
   397         QTimer::singleShot(10, this, SLOT(feedback()));
       
   398         emit stateChanged(deviceState);
       
   399     }
       
   400 }
       
   401 
       
   402 void QAudioOutputPrivate::suspend()
       
   403 {
       
   404     if(deviceState == QAudio::ActiveState) {
       
   405         waveOutPause(hWaveOut);
       
   406         deviceState = QAudio::SuspendState;
       
   407         errorState = QAudio::NoError;
       
   408         emit stateChanged(deviceState);
       
   409     }
       
   410 }
       
   411 
       
   412 void QAudioOutputPrivate::feedback()
       
   413 {
       
   414 #ifdef DEBUG_AUDIO
       
   415     QTime now(QTime::currentTime());
       
   416     qDebug()<<now.second()<<"s "<<now.msec()<<"ms :feedback()";
       
   417 #endif
       
   418     bytesAvailable = bytesFree();
       
   419 
       
   420     if(!(deviceState==QAudio::StopState||deviceState==QAudio::SuspendState)) {
       
   421         if(bytesAvailable >= period_size)
       
   422             QMetaObject::invokeMethod(this, "deviceReady", Qt::QueuedConnection);
       
   423     }
       
   424 }
       
   425 
       
   426 bool QAudioOutputPrivate::deviceReady()
       
   427 {
       
   428     if(pullMode) {
       
   429         int chunks = bytesAvailable/period_size;
       
   430 #ifdef DEBUG_AUDIO
       
   431         qDebug()<<"deviceReady() avail="<<bytesAvailable<<" bytes, period size="<<period_size<<" bytes";
       
   432         qDebug()<<"deviceReady() no. of chunks that can fit ="<<chunks<<", chunks in bytes ="<<chunks*period_size;
       
   433 #endif
       
   434         bool startup = false;
       
   435         if(totalTimeValue == 0)
       
   436 	    startup = true;
       
   437 
       
   438 	bool full=false;
       
   439 	EnterCriticalSection(&waveOutCriticalSection);
       
   440 	if(waveFreeBlockCount==0) full = true;
       
   441 	LeaveCriticalSection(&waveOutCriticalSection);
       
   442 	if (full){
       
   443 #ifdef DEBUG_AUDIO
       
   444             qDebug() << "Skipping data as unable to write";
       
   445 #endif
       
   446 	    if((timeStamp.elapsed() + elapsedTimeOffset) > intervalTime ) {
       
   447                 emit notify();
       
   448 		elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
       
   449 		timeStamp.restart();
       
   450 	    }
       
   451 	    return true;
       
   452 	}
       
   453 
       
   454         if(startup)
       
   455 	    waveOutPause(hWaveOut);
       
   456         int input = period_size*chunks;
       
   457         int l = audioSource->read(audioBuffer,input);
       
   458         if(l > 0) {
       
   459             int out= write(audioBuffer,l);
       
   460             if(out > 0)
       
   461                 deviceState = QAudio::ActiveState;
       
   462 	    if(startup)
       
   463 	        waveOutRestart(hWaveOut);
       
   464         } else if(l == 0) {
       
   465             bytesAvailable = bytesFree();
       
   466 
       
   467             int check = 0;
       
   468             EnterCriticalSection(&waveOutCriticalSection);
       
   469             check = waveFreeBlockCount;
       
   470             LeaveCriticalSection(&waveOutCriticalSection);
       
   471             if(check == buffer_size/period_size) {
       
   472                 errorState = QAudio::UnderrunError;
       
   473                 deviceState = QAudio::IdleState;
       
   474                 emit stateChanged(deviceState);
       
   475             }
       
   476 
       
   477         } else if(l < 0) {
       
   478             bytesAvailable = bytesFree();
       
   479             errorState = QAudio::IOError;
       
   480         }
       
   481     }
       
   482     if(deviceState != QAudio::ActiveState)
       
   483         return true;
       
   484 
       
   485     if((timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
       
   486         emit notify();
       
   487 	elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
       
   488         timeStamp.restart();
       
   489     }
       
   490 
       
   491     return true;
       
   492 }
       
   493 
       
   494 qint64 QAudioOutputPrivate::clock() const
       
   495 {
       
   496     if (deviceState == QAudio::StopState)
       
   497         return 0;
       
   498 
       
   499     return timeStampOpened.elapsed()*1000;
       
   500 }
       
   501 
       
   502 QAudio::Error QAudioOutputPrivate::error() const
       
   503 {
       
   504     return errorState;
       
   505 }
       
   506 
       
   507 QAudio::State QAudioOutputPrivate::state() const
       
   508 {
       
   509     return deviceState;
       
   510 }
       
   511 
       
   512 void QAudioOutputPrivate::reset()
       
   513 {
       
   514     close();
       
   515 }
       
   516 
       
   517 OutputPrivate::OutputPrivate(QAudioOutputPrivate* audio)
       
   518 {
       
   519     audioDevice = qobject_cast<QAudioOutputPrivate*>(audio);
       
   520 }
       
   521 
       
   522 OutputPrivate::~OutputPrivate() {}
       
   523 
       
   524 qint64 OutputPrivate::readData( char* data, qint64 len)
       
   525 {
       
   526     Q_UNUSED(data)
       
   527     Q_UNUSED(len)
       
   528 
       
   529     return 0;
       
   530 }
       
   531 
       
   532 qint64 OutputPrivate::writeData(const char* data, qint64 len)
       
   533 {
       
   534     int retry = 0;
       
   535     qint64 written = 0;
       
   536 
       
   537     if((audioDevice->deviceState == QAudio::ActiveState)
       
   538             ||(audioDevice->deviceState == QAudio::IdleState)) {
       
   539         qint64 l = len;
       
   540         while(written < l) {
       
   541             int chunk = audioDevice->write(data+written,(l-written));
       
   542             if(chunk <= 0)
       
   543                 retry++;
       
   544             else
       
   545                 written+=chunk;
       
   546 
       
   547             if(retry > 10)
       
   548                 return written;
       
   549         }
       
   550         audioDevice->deviceState = QAudio::ActiveState;
       
   551     }
       
   552     return written;
       
   553 }
       
   554 
       
   555 QT_END_NAMESPACE