source: trunk/src/multimedia/audio/qaudioinput_win32_p.cpp@ 890

Last change on this file since 890 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 17.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 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
54#include "qaudioinput_win32_p.h"
55
56QT_BEGIN_NAMESPACE
57
58//#define DEBUG_AUDIO 1
59
60QAudioInputPrivate::QAudioInputPrivate(const QByteArray &device, const QAudioFormat& audioFormat):
61 settings(audioFormat)
62{
63 bytesAvailable = 0;
64 buffer_size = 0;
65 period_size = 0;
66 m_device = device;
67 totalTimeValue = 0;
68 intervalTime = 1000;
69 errorState = QAudio::NoError;
70 deviceState = QAudio::StoppedState;
71 audioSource = 0;
72 pullMode = true;
73 resuming = false;
74 finished = false;
75}
76
77QAudioInputPrivate::~QAudioInputPrivate()
78{
79 stop();
80}
81
82void QT_WIN_CALLBACK QAudioInputPrivate::waveInProc( HWAVEIN hWaveIn, UINT uMsg,
83 DWORD dwInstance, DWORD dwParam1, DWORD dwParam2 )
84{
85 Q_UNUSED(dwParam1)
86 Q_UNUSED(dwParam2)
87 Q_UNUSED(hWaveIn)
88
89 QAudioInputPrivate* qAudio;
90 qAudio = (QAudioInputPrivate*)(dwInstance);
91 if(!qAudio)
92 return;
93
94 QMutexLocker(&qAudio->mutex);
95
96 switch(uMsg) {
97 case WIM_OPEN:
98 break;
99 case WIM_DATA:
100 if(qAudio->waveFreeBlockCount > 0)
101 qAudio->waveFreeBlockCount--;
102 qAudio->feedback();
103 break;
104 case WIM_CLOSE:
105 qAudio->finished = true;
106 break;
107 default:
108 return;
109 }
110}
111
112WAVEHDR* QAudioInputPrivate::allocateBlocks(int size, int count)
113{
114 int i;
115 unsigned char* buffer;
116 WAVEHDR* blocks;
117 DWORD totalBufferSize = (size + sizeof(WAVEHDR))*count;
118
119 if((buffer=(unsigned char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
120 totalBufferSize)) == 0) {
121 qWarning("QAudioInput: Memory allocation error");
122 return 0;
123 }
124 blocks = (WAVEHDR*)buffer;
125 buffer += sizeof(WAVEHDR)*count;
126 for(i = 0; i < count; i++) {
127 blocks[i].dwBufferLength = size;
128 blocks[i].lpData = (LPSTR)buffer;
129 blocks[i].dwBytesRecorded=0;
130 blocks[i].dwUser = 0L;
131 blocks[i].dwFlags = 0L;
132 blocks[i].dwLoops = 0L;
133 result = waveInPrepareHeader(hWaveIn,&blocks[i], sizeof(WAVEHDR));
134 if(result != MMSYSERR_NOERROR) {
135 qWarning("QAudioInput: Can't prepare block %d",i);
136 return 0;
137 }
138 buffer += size;
139 }
140 return blocks;
141}
142
143void QAudioInputPrivate::freeBlocks(WAVEHDR* blockArray)
144{
145 WAVEHDR* blocks = blockArray;
146
147 int count = buffer_size/period_size;
148
149 for(int i = 0; i < count; i++) {
150 waveInUnprepareHeader(hWaveIn,blocks, sizeof(WAVEHDR));
151 blocks++;
152 }
153 HeapFree(GetProcessHeap(), 0, blockArray);
154}
155
156QAudio::Error QAudioInputPrivate::error() const
157{
158 return errorState;
159}
160
161QAudio::State QAudioInputPrivate::state() const
162{
163 return deviceState;
164}
165
166QAudioFormat QAudioInputPrivate::format() const
167{
168 return settings;
169}
170
171QIODevice* QAudioInputPrivate::start(QIODevice* device)
172{
173 if(deviceState != QAudio::StoppedState)
174 close();
175
176 if(!pullMode && audioSource) {
177 delete audioSource;
178 }
179
180 if(device) {
181 //set to pull mode
182 pullMode = true;
183 audioSource = device;
184 deviceState = QAudio::ActiveState;
185 } else {
186 //set to push mode
187 pullMode = false;
188 deviceState = QAudio::IdleState;
189 audioSource = new InputPrivate(this);
190 audioSource->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
191 }
192
193 if( !open() )
194 return 0;
195
196 emit stateChanged(deviceState);
197
198 return audioSource;
199}
200
201void QAudioInputPrivate::stop()
202{
203 if(deviceState == QAudio::StoppedState)
204 return;
205
206 close();
207 emit stateChanged(deviceState);
208}
209
210bool QAudioInputPrivate::open()
211{
212#ifdef DEBUG_AUDIO
213 QTime now(QTime::currentTime());
214 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :open()";
215#endif
216 header = 0;
217 period_size = 0;
218
219 if (!settings.isValid()) {
220 qWarning("QAudioInput: open error, invalid format.");
221 } else if (settings.channels() <= 0) {
222 qWarning("QAudioInput: open error, invalid number of channels (%d).",
223 settings.channels());
224 } else if (settings.sampleSize() <= 0) {
225 qWarning("QAudioInput: open error, invalid sample size (%d).",
226 settings.sampleSize());
227 } else if (settings.frequency() < 8000 || settings.frequency() > 48000) {
228 qWarning("QAudioInput: open error, frequency out of range (%d).", settings.frequency());
229 } else if (buffer_size == 0) {
230
231 buffer_size
232 = (settings.frequency()
233 * settings.channels()
234 * settings.sampleSize()
235#ifndef Q_OS_WINCE // Default buffer size, 200ms, default period size is 40ms
236 + 39) / 40;
237 period_size = buffer_size / 5;
238 } else {
239 period_size = buffer_size / 5;
240#else // For wince reduce size to 40ms for buffer size and 20ms period
241 + 199) / 200;
242 period_size = buffer_size / 2;
243 } else {
244 period_size = buffer_size / 2;
245#endif
246 }
247
248 if (period_size == 0) {
249 errorState = QAudio::OpenError;
250 deviceState = QAudio::StoppedState;
251 emit stateChanged(deviceState);
252 return false;
253 }
254
255 timeStamp.restart();
256 elapsedTimeOffset = 0;
257 wfx.nSamplesPerSec = settings.frequency();
258 wfx.wBitsPerSample = settings.sampleSize();
259 wfx.nChannels = settings.channels();
260 wfx.cbSize = 0;
261
262 wfx.wFormatTag = WAVE_FORMAT_PCM;
263 wfx.nBlockAlign = (wfx.wBitsPerSample >> 3) * wfx.nChannels;
264 wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
265
266 UINT_PTR devId = WAVE_MAPPER;
267
268 WAVEINCAPS wic;
269 unsigned long iNumDevs,ii;
270 iNumDevs = waveInGetNumDevs();
271 for(ii=0;ii<iNumDevs;ii++) {
272 if(waveInGetDevCaps(ii, &wic, sizeof(WAVEINCAPS))
273 == MMSYSERR_NOERROR) {
274 QString tmp;
275 tmp = QString((const QChar *)wic.szPname);
276 if(tmp.compare(QLatin1String(m_device)) == 0) {
277 devId = ii;
278 break;
279 }
280 }
281 }
282
283 if(waveInOpen(&hWaveIn, devId, &wfx,
284 (DWORD_PTR)&waveInProc,
285 (DWORD_PTR) this,
286 CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
287 errorState = QAudio::OpenError;
288 deviceState = QAudio::StoppedState;
289 emit stateChanged(deviceState);
290 qWarning("QAudioInput: failed to open audio device");
291 return false;
292 }
293 waveBlocks = allocateBlocks(period_size, buffer_size/period_size);
294
295 if(waveBlocks == 0) {
296 errorState = QAudio::OpenError;
297 deviceState = QAudio::StoppedState;
298 emit stateChanged(deviceState);
299 qWarning("QAudioInput: failed to allocate blocks. open failed");
300 return false;
301 }
302
303 mutex.lock();
304 waveFreeBlockCount = buffer_size/period_size;
305 mutex.unlock();
306
307 waveCurrentBlock = 0;
308
309 for(int i=0; i<buffer_size/period_size; i++) {
310 result = waveInAddBuffer(hWaveIn, &waveBlocks[i], sizeof(WAVEHDR));
311 if(result != MMSYSERR_NOERROR) {
312 qWarning("QAudioInput: failed to setup block %d,err=%d",i,result);
313 errorState = QAudio::OpenError;
314 deviceState = QAudio::StoppedState;
315 emit stateChanged(deviceState);
316 return false;
317 }
318 }
319 result = waveInStart(hWaveIn);
320 if(result) {
321 qWarning("QAudioInput: failed to start audio input");
322 errorState = QAudio::OpenError;
323 deviceState = QAudio::StoppedState;
324 emit stateChanged(deviceState);
325 return false;
326 }
327 timeStampOpened.restart();
328 elapsedTimeOffset = 0;
329 totalTimeValue = 0;
330 errorState = QAudio::NoError;
331 return true;
332}
333
334void QAudioInputPrivate::close()
335{
336 if(deviceState == QAudio::StoppedState)
337 return;
338
339 deviceState = QAudio::StoppedState;
340 waveInReset(hWaveIn);
341 waveInClose(hWaveIn);
342
343 int count = 0;
344 while(!finished && count < 500) {
345 count++;
346 Sleep(10);
347 }
348
349 mutex.lock();
350 for(int i=0; i<waveFreeBlockCount; i++)
351 waveInUnprepareHeader(hWaveIn,&waveBlocks[i],sizeof(WAVEHDR));
352 freeBlocks(waveBlocks);
353 mutex.unlock();
354}
355
356int QAudioInputPrivate::bytesReady() const
357{
358 if(period_size == 0 || buffer_size == 0)
359 return 0;
360
361 int buf = ((buffer_size/period_size)-waveFreeBlockCount)*period_size;
362 if(buf < 0)
363 buf = 0;
364 return buf;
365}
366
367qint64 QAudioInputPrivate::read(char* data, qint64 len)
368{
369 bool done = false;
370
371 char* p = data;
372 qint64 l = 0;
373 qint64 written = 0;
374 while(!done) {
375 // Read in some audio data
376 if(waveBlocks[header].dwBytesRecorded > 0 && waveBlocks[header].dwFlags & WHDR_DONE) {
377 if(pullMode) {
378 l = audioSource->write(waveBlocks[header].lpData,
379 waveBlocks[header].dwBytesRecorded);
380#ifdef DEBUG_AUDIO
381 qDebug()<<"IN: "<<waveBlocks[header].dwBytesRecorded<<", OUT: "<<l;
382#endif
383 if(l < 0) {
384 // error
385 qWarning("QAudioInput: IOError");
386 errorState = QAudio::IOError;
387
388 } else if(l == 0) {
389 // cant write to IODevice
390 qWarning("QAudioInput: IOError, can't write to QIODevice");
391 errorState = QAudio::IOError;
392
393 } else {
394 totalTimeValue += waveBlocks[header].dwBytesRecorded;
395 errorState = QAudio::NoError;
396 if (deviceState != QAudio::ActiveState) {
397 deviceState = QAudio::ActiveState;
398 emit stateChanged(deviceState);
399 }
400 resuming = false;
401 }
402 } else {
403 l = qMin<qint64>(len, waveBlocks[header].dwBytesRecorded);
404 // push mode
405 memcpy(p, waveBlocks[header].lpData, l);
406
407 len -= l;
408
409#ifdef DEBUG_AUDIO
410 qDebug()<<"IN: "<<waveBlocks[header].dwBytesRecorded<<", OUT: "<<l;
411#endif
412 totalTimeValue += waveBlocks[header].dwBytesRecorded;
413 errorState = QAudio::NoError;
414 if (deviceState != QAudio::ActiveState) {
415 deviceState = QAudio::ActiveState;
416 emit stateChanged(deviceState);
417 }
418 resuming = false;
419 }
420 } else {
421 //no data, not ready yet, next time
422 break;
423 }
424
425 waveInUnprepareHeader(hWaveIn,&waveBlocks[header], sizeof(WAVEHDR));
426
427 mutex.lock();
428 waveFreeBlockCount++;
429 mutex.unlock();
430
431 waveBlocks[header].dwBytesRecorded=0;
432 waveBlocks[header].dwFlags = 0L;
433 result = waveInPrepareHeader(hWaveIn,&waveBlocks[header], sizeof(WAVEHDR));
434 if(result != MMSYSERR_NOERROR) {
435 result = waveInPrepareHeader(hWaveIn,&waveBlocks[header], sizeof(WAVEHDR));
436 qWarning("QAudioInput: failed to prepare block %d,err=%d",header,result);
437 errorState = QAudio::IOError;
438
439 mutex.lock();
440 waveFreeBlockCount--;
441 mutex.unlock();
442
443 return 0;
444 }
445 result = waveInAddBuffer(hWaveIn, &waveBlocks[header], sizeof(WAVEHDR));
446 if(result != MMSYSERR_NOERROR) {
447 qWarning("QAudioInput: failed to setup block %d,err=%d",header,result);
448 errorState = QAudio::IOError;
449
450 mutex.lock();
451 waveFreeBlockCount--;
452 mutex.unlock();
453
454 return 0;
455 }
456 header++;
457 if(header >= buffer_size/period_size)
458 header = 0;
459 p+=l;
460
461 mutex.lock();
462 if(!pullMode) {
463 if(len < period_size || waveFreeBlockCount == buffer_size/period_size)
464 done = true;
465 } else {
466 if(waveFreeBlockCount == buffer_size/period_size)
467 done = true;
468 }
469 mutex.unlock();
470
471 written+=l;
472 }
473#ifdef DEBUG_AUDIO
474 qDebug()<<"read in len="<<written;
475#endif
476 return written;
477}
478
479void QAudioInputPrivate::resume()
480{
481 if(deviceState == QAudio::SuspendedState) {
482 deviceState = QAudio::ActiveState;
483 for(int i=0; i<buffer_size/period_size; i++) {
484 result = waveInAddBuffer(hWaveIn, &waveBlocks[i], sizeof(WAVEHDR));
485 if(result != MMSYSERR_NOERROR) {
486 qWarning("QAudioInput: failed to setup block %d,err=%d",i,result);
487 errorState = QAudio::OpenError;
488 deviceState = QAudio::StoppedState;
489 emit stateChanged(deviceState);
490 return;
491 }
492 }
493
494 mutex.lock();
495 waveFreeBlockCount = buffer_size/period_size;
496 mutex.unlock();
497
498 waveCurrentBlock = 0;
499 header = 0;
500 resuming = true;
501 waveInStart(hWaveIn);
502 QTimer::singleShot(20,this,SLOT(feedback()));
503 emit stateChanged(deviceState);
504 }
505}
506
507void QAudioInputPrivate::setBufferSize(int value)
508{
509 buffer_size = value;
510}
511
512int QAudioInputPrivate::bufferSize() const
513{
514 return buffer_size;
515}
516
517int QAudioInputPrivate::periodSize() const
518{
519 return period_size;
520}
521
522void QAudioInputPrivate::setNotifyInterval(int ms)
523{
524 intervalTime = qMax(0, ms);
525}
526
527int QAudioInputPrivate::notifyInterval() const
528{
529 return intervalTime;
530}
531
532qint64 QAudioInputPrivate::processedUSecs() const
533{
534 if (deviceState == QAudio::StoppedState)
535 return 0;
536 qint64 result = qint64(1000000) * totalTimeValue /
537 (settings.channels()*(settings.sampleSize()/8)) /
538 settings.frequency();
539
540 return result;
541}
542
543void QAudioInputPrivate::suspend()
544{
545 if(deviceState == QAudio::ActiveState) {
546 waveInReset(hWaveIn);
547 deviceState = QAudio::SuspendedState;
548 emit stateChanged(deviceState);
549 }
550}
551
552void QAudioInputPrivate::feedback()
553{
554#ifdef DEBUG_AUDIO
555 QTime now(QTime::currentTime());
556 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :feedback() INPUT "<<this;
557#endif
558 if(!(deviceState==QAudio::StoppedState||deviceState==QAudio::SuspendedState))
559 QMetaObject::invokeMethod(this, "deviceReady", Qt::QueuedConnection);
560}
561
562bool QAudioInputPrivate::deviceReady()
563{
564 bytesAvailable = bytesReady();
565#ifdef DEBUG_AUDIO
566 QTime now(QTime::currentTime());
567 qDebug()<<now.second()<<"s "<<now.msec()<<"ms :deviceReady() INPUT";
568#endif
569 if(deviceState != QAudio::ActiveState && deviceState != QAudio::IdleState)
570 return true;
571
572 if(pullMode) {
573 // reads some audio data and writes it to QIODevice
574 read(0, buffer_size);
575 } else {
576 // emits readyRead() so user will call read() on QIODevice to get some audio data
577 InputPrivate* a = qobject_cast<InputPrivate*>(audioSource);
578 a->trigger();
579 }
580
581 if(intervalTime && (timeStamp.elapsed() + elapsedTimeOffset) > intervalTime) {
582 emit notify();
583 elapsedTimeOffset = timeStamp.elapsed() + elapsedTimeOffset - intervalTime;
584 timeStamp.restart();
585 }
586 return true;
587}
588
589qint64 QAudioInputPrivate::elapsedUSecs() const
590{
591 if (deviceState == QAudio::StoppedState)
592 return 0;
593
594 return timeStampOpened.elapsed()*1000;
595}
596
597void QAudioInputPrivate::reset()
598{
599 close();
600}
601
602InputPrivate::InputPrivate(QAudioInputPrivate* audio)
603{
604 audioDevice = qobject_cast<QAudioInputPrivate*>(audio);
605}
606
607InputPrivate::~InputPrivate() {}
608
609qint64 InputPrivate::readData( char* data, qint64 len)
610{
611 // push mode, user read() called
612 if(audioDevice->deviceState != QAudio::ActiveState &&
613 audioDevice->deviceState != QAudio::IdleState)
614 return 0;
615 // Read in some audio data
616 return audioDevice->read(data,len);
617}
618
619qint64 InputPrivate::writeData(const char* data, qint64 len)
620{
621 Q_UNUSED(data)
622 Q_UNUSED(len)
623
624 emit readyRead();
625 return 0;
626}
627
628void InputPrivate::trigger()
629{
630 emit readyRead();
631}
632
633QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.