source: trunk/src/multimedia/audio/qaudiooutput_win32_p.cpp@ 561

Last change on this file since 561 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

  • Property svn:eol-style set to native
File size: 15.3 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtMultimedia module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
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** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
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
57QT_BEGIN_NAMESPACE
58
59static CRITICAL_SECTION waveOutCriticalSection;
60
61static const int minimumIntervalTime = 50;
62
63QAudioOutputPrivate::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::StoppedState;
75 audioSource = 0;
76 pullMode = true;
77 finished = false;
78 InitializeCriticalSection(&waveOutCriticalSection);
79}
80
81QAudioOutputPrivate::~QAudioOutputPrivate()
82{
83 EnterCriticalSection(&waveOutCriticalSection);
84 finished = true;
85 LeaveCriticalSection(&waveOutCriticalSection);
86
87 close();
88 DeleteCriticalSection(&waveOutCriticalSection);
89}
90
91void 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
126WAVEHDR* 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
148void QAudioOutputPrivate::freeBlocks(WAVEHDR* blockArray)
149{
150 WAVEHDR* blocks = blockArray;
151
152 int count = buffer_size/period_size;
153
154 for(int i = 0; i < count; i++) {
155 waveOutUnprepareHeader(hWaveOut,&blocks[i], sizeof(WAVEHDR));
156 blocks+=sizeof(WAVEHDR);
157 }
158 HeapFree(GetProcessHeap(), 0, blockArray);
159}
160
161QAudioFormat QAudioOutputPrivate::format() const
162{
163 return settings;
164}
165
166QIODevice* QAudioOutputPrivate::start(QIODevice* device)
167{
168 if(deviceState != QAudio::StoppedState)
169 close();
170
171 if(!pullMode && audioSource) {
172 delete audioSource;
173 }
174
175 if(device) {
176 //set to pull mode
177 pullMode = true;
178 audioSource = device;
179 deviceState = QAudio::ActiveState;
180 } else {
181 //set to push mode
182 pullMode = false;
183 audioSource = new OutputPrivate(this);
184 audioSource->open(QIODevice::WriteOnly|QIODevice::Unbuffered);
185 deviceState = QAudio::IdleState;
186 }
187
188 if( !open() )
189 return 0;
190
191 emit stateChanged(deviceState);
192
193 return audioSource;
194}
195
196void QAudioOutputPrivate::stop()
197{
198 if(deviceState == QAudio::StoppedState)
199 return;
200 close();
201 if(!pullMode && audioSource) {
202 delete audioSource;
203 audioSource = 0;
204 }
205 emit stateChanged(deviceState);
206}
207
208bool QAudioOutputPrivate::open()
209{
210#ifdef DEBUG_AUDIO
211 QTime now(QTime::currentTime());
212 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
213#endif
214 if(buffer_size == 0) {
215 // Default buffer size, 200ms, default period size is 40ms
216 buffer_size = settings.frequency()*settings.channels()*(settings.sampleSize()/8)*0.2;
217 period_size = buffer_size/5;
218 } else {
219 period_size = buffer_size/5;
220 }
221 waveBlocks = allocateBlocks(period_size, buffer_size/period_size);
222
223 EnterCriticalSection(&waveOutCriticalSection);
224 waveFreeBlockCount = buffer_size/period_size;
225 LeaveCriticalSection(&waveOutCriticalSection);
226
227 waveCurrentBlock = 0;
228
229 if(audioBuffer == 0)
230 audioBuffer = new char[buffer_size];
231
232 timeStamp.restart();
233 elapsedTimeOffset = 0;
234
235 wfx.nSamplesPerSec = settings.frequency();
236 wfx.wBitsPerSample = settings.sampleSize();
237 wfx.nChannels = settings.channels();
238 wfx.cbSize = 0;
239
240 wfx.wFormatTag = WAVE_FORMAT_PCM;
241 wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
242 wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
243
244 UINT_PTR devId = WAVE_MAPPER;
245
246 WAVEOUTCAPS woc;
247 unsigned long iNumDevs,ii;
248 iNumDevs = waveOutGetNumDevs();
249 for(ii=0;ii<iNumDevs;ii++) {
250 if(waveOutGetDevCaps(ii, &woc, sizeof(WAVEOUTCAPS))
251 == MMSYSERR_NOERROR) {
252 QString tmp;
253 tmp = QString::fromUtf16((const unsigned short*)woc.szPname);
254 if(tmp.compare(QLatin1String(m_device)) == 0) {
255 devId = ii;
256 break;
257 }
258 }
259 }
260
261 if(waveOutOpen(&hWaveOut, devId, &wfx,
262 (DWORD_PTR)&waveOutProc,
263 (DWORD_PTR) this,
264 CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
265 errorState = QAudio::OpenError;
266 deviceState = QAudio::StoppedState;
267 emit stateChanged(deviceState);
268 qWarning("QAudioOutput: open error");
269 return false;
270 }
271
272 totalTimeValue = 0;
273 timeStampOpened.restart();
274 elapsedTimeOffset = 0;
275
276 errorState = QAudio::NoError;
277 if(pullMode) {
278 deviceState = QAudio::ActiveState;
279 QTimer::singleShot(10, this, SLOT(feedback()));
280 } else
281 deviceState = QAudio::IdleState;
282
283 return true;
284}
285
286void QAudioOutputPrivate::close()
287{
288 if(deviceState == QAudio::StoppedState)
289 return;
290
291 deviceState = QAudio::StoppedState;
292 int delay = (buffer_size-bytesFree())*1000/(settings.frequency()
293 *settings.channels()*(settings.sampleSize()/8));
294 waveOutReset(hWaveOut);
295 Sleep(delay+10);
296
297 freeBlocks(waveBlocks);
298 waveOutClose(hWaveOut);
299 delete [] audioBuffer;
300 audioBuffer = 0;
301 buffer_size = 0;
302}
303
304int QAudioOutputPrivate::bytesFree() const
305{
306 int buf;
307 buf = waveFreeBlockCount*period_size;
308
309 return buf;
310}
311
312int QAudioOutputPrivate::periodSize() const
313{
314 return period_size;
315}
316
317void QAudioOutputPrivate::setBufferSize(int value)
318{
319 if(deviceState == QAudio::StoppedState)
320 buffer_size = value;
321}
322
323int QAudioOutputPrivate::bufferSize() const
324{
325 return buffer_size;
326}
327
328void QAudioOutputPrivate::setNotifyInterval(int ms)
329{
330 if(ms >= minimumIntervalTime)
331 intervalTime = ms;
332 else
333 intervalTime = minimumIntervalTime;
334}
335
336int QAudioOutputPrivate::notifyInterval() const
337{
338 return intervalTime;
339}
340
341qint64 QAudioOutputPrivate::processedUSecs() const
342{
343 return totalTimeValue;
344}
345
346qint64 QAudioOutputPrivate::write( const char *data, qint64 len )
347{
348 // Write out some audio data
349
350 char* p = (char*)data;
351 int l = (int)len;
352
353 WAVEHDR* current;
354 int remain;
355 current = &waveBlocks[waveCurrentBlock];
356 while(l > 0) {
357 EnterCriticalSection(&waveOutCriticalSection);
358 if(waveFreeBlockCount==0) {
359 LeaveCriticalSection(&waveOutCriticalSection);
360 break;
361 }
362 LeaveCriticalSection(&waveOutCriticalSection);
363
364 if(current->dwFlags & WHDR_PREPARED)
365 waveOutUnprepareHeader(hWaveOut, current, sizeof(WAVEHDR));
366
367 if(l < period_size)
368 remain = l;
369 else
370 remain = period_size;
371 memcpy(current->lpData, p, remain);
372
373 l -= remain;
374 p += remain;
375 current->dwBufferLength = remain;
376 waveOutPrepareHeader(hWaveOut, current, sizeof(WAVEHDR));
377 waveOutWrite(hWaveOut, current, sizeof(WAVEHDR));
378
379 EnterCriticalSection(&waveOutCriticalSection);
380 waveFreeBlockCount--;
381 LeaveCriticalSection(&waveOutCriticalSection);
382#ifdef DEBUG_AUDIO
383 EnterCriticalSection(&waveOutCriticalSection);
384 qDebug("write out l=%d, waveFreeBlockCount=%d",
385 current->dwBufferLength,waveFreeBlockCount);
386 LeaveCriticalSection(&waveOutCriticalSection);
387#endif
388 totalTimeValue += current->dwBufferLength
389 /(settings.channels()*(settings.sampleSize()/8))
390 *1000000/settings.frequency();;
391 waveCurrentBlock++;
392 waveCurrentBlock %= buffer_size/period_size;
393 current = &waveBlocks[waveCurrentBlock];
394 current->dwUser = 0;
395 }
396 return (len-l);
397}
398
399void QAudioOutputPrivate::resume()
400{
401 if(deviceState == QAudio::SuspendedState) {
402 deviceState = QAudio::ActiveState;
403 errorState = QAudio::NoError;
404 waveOutRestart(hWaveOut);
405 QTimer::singleShot(10, this, SLOT(feedback()));
406 emit stateChanged(deviceState);
407 }
408}
409
410void QAudioOutputPrivate::suspend()
411{
412 if(deviceState == QAudio::ActiveState) {
413 waveOutPause(hWaveOut);
414 deviceState = QAudio::SuspendedState;
415 errorState = QAudio::NoError;
416 emit stateChanged(deviceState);
417 }
418}
419
420void QAudioOutputPrivate::feedback()
421{
422#ifdef DEBUG_AUDIO
423 QTime now(QTime::currentTime());
424 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :feedback()";
425#endif
426 bytesAvailable = bytesFree();
427
428 if(!(deviceState==QAudio::StoppedState||deviceState==QAudio::SuspendedState)) {
429 if(bytesAvailable >= period_size)
430 QMetaObject::invokeMethod(this, "deviceReady", Qt::QueuedConnection);
431 }
432}
433
434bool QAudioOutputPrivate::deviceReady()
435{
436 if(pullMode) {
437 int chunks = bytesAvailable/period_size;
438#ifdef DEBUG_AUDIO
439 qDebug()<<"deviceReady() avail="<<bytesAvailable<<" bytes, period size="<<period_size<<" bytes";
440 qDebug()<<"deviceReady() no. of chunks that can fit ="<<chunks<<", chunks in bytes ="<<chunks*period_size;
441#endif
442 bool startup = false;
443 if(totalTimeValue == 0)
444 startup = true;
445
446 bool full=false;
447 EnterCriticalSection(&waveOutCriticalSection);
448 if(waveFreeBlockCount==0) full = true;
449 LeaveCriticalSection(&waveOutCriticalSection);
450 if (full){
451#ifdef DEBUG_AUDIO
452 qDebug() << "Skipping data as unable to write";
453#endif
454 if((timeStamp.elapsed() + elapsedTimeOffset) > intervalTime ) {
455 emit notify();
456 elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
457 timeStamp.restart();
458 }
459 return true;
460 }
461
462 if(startup)
463 waveOutPause(hWaveOut);
464 int input = period_size*chunks;
465 int l = audioSource->read(audioBuffer,input);
466 if(l > 0) {
467 int out= write(audioBuffer,l);
468 if(out > 0)
469 deviceState = QAudio::ActiveState;
470 if(startup)
471 waveOutRestart(hWaveOut);
472 } else if(l == 0) {
473 bytesAvailable = bytesFree();
474
475 int check = 0;
476 EnterCriticalSection(&waveOutCriticalSection);
477 check = waveFreeBlockCount;
478 LeaveCriticalSection(&waveOutCriticalSection);
479 if(check == buffer_size/period_size) {
480 errorState = QAudio::UnderrunError;
481 deviceState = QAudio::IdleState;
482 emit stateChanged(deviceState);
483 }
484
485 } else if(l < 0) {
486 bytesAvailable = bytesFree();
487 errorState = QAudio::IOError;
488 }
489 }
490 if(deviceState != QAudio::ActiveState)
491 return true;
492
493 if((timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
494 emit notify();
495 elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
496 timeStamp.restart();
497 }
498
499 return true;
500}
501
502qint64 QAudioOutputPrivate::elapsedUSecs() const
503{
504 if (deviceState == QAudio::StoppedState)
505 return 0;
506
507 return timeStampOpened.elapsed()*1000;
508}
509
510QAudio::Error QAudioOutputPrivate::error() const
511{
512 return errorState;
513}
514
515QAudio::State QAudioOutputPrivate::state() const
516{
517 return deviceState;
518}
519
520void QAudioOutputPrivate::reset()
521{
522 close();
523}
524
525OutputPrivate::OutputPrivate(QAudioOutputPrivate* audio)
526{
527 audioDevice = qobject_cast<QAudioOutputPrivate*>(audio);
528}
529
530OutputPrivate::~OutputPrivate() {}
531
532qint64 OutputPrivate::readData( char* data, qint64 len)
533{
534 Q_UNUSED(data)
535 Q_UNUSED(len)
536
537 return 0;
538}
539
540qint64 OutputPrivate::writeData(const char* data, qint64 len)
541{
542 int retry = 0;
543 qint64 written = 0;
544
545 if((audioDevice->deviceState == QAudio::ActiveState)
546 ||(audioDevice->deviceState == QAudio::IdleState)) {
547 qint64 l = len;
548 while(written < l) {
549 int chunk = audioDevice->write(data+written,(l-written));
550 if(chunk <= 0)
551 retry++;
552 else
553 written+=chunk;
554
555 if(retry > 10)
556 return written;
557 }
558 audioDevice->deviceState = QAudio::ActiveState;
559 }
560 return written;
561}
562
563QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.