source: trunk/src/3rdparty/phonon/waveout/mediaobject.cpp

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

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

File size: 23.7 KB
Line 
1/* This file is part of the KDE project.
2
3Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
5This library is free software: you can redistribute it and/or modify
6it under the terms of the GNU Lesser General Public License as published by
7the Free Software Foundation, either version 2.1 or 3 of the License.
8
9This library is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12GNU Lesser General Public License for more details.
13
14You should have received a copy of the GNU Lesser General Public License
15along with this library. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "mediaobject.h"
19#include "audiooutput.h"
20
21#include <QtCore/QVector>
22#include <QtCore/QTimerEvent>
23#include <QtCore/QTimer>
24#include <QtCore/QTime>
25#include <QtCore/QLibrary>
26#include <QtCore/QUrl>
27#include <QtCore/QWriteLocker>
28
29#include <phonon/streaminterface.h>
30
31
32#define WAVEHEADER_OFFSET_FORMATTAG 20
33#define WAVEHEADER_OFFSET_CHANNELS 22
34#define WAVEHEADER_OFFSET_SAMPLESPERSEC 24
35#define WAVEHEADER_OFFSET_AVGBYTESPERSEC 28
36#define WAVEHEADER_OFFSET_BLOCKALIGN 32
37#define WAVEHEADER_OFFSET_BITSPERSAMPLE 34
38#define WAVEHEADER_OFFSET_DATA 44
39#define WAVEHEADER_SIZE WAVEHEADER_OFFSET_DATA
40
41QT_BEGIN_NAMESPACE
42
43namespace Phonon
44{
45 namespace WaveOut
46 {
47 static unsigned int buffer_size = (16 * 1024 * 4);
48
49 QString getErrorText(MMRESULT error)
50 {
51 ushort b[256];
52 waveOutGetErrorText(error, (LPWSTR)b, 256);
53 return QString((const QChar *)b);
54 }
55
56 class WorkerThread : public QThread
57 {
58 Q_OBJECT
59 public slots:
60 void stream(QIODevice *file, QByteArray *buffer, bool *finished);
61 };
62
63 void WorkerThread::stream(QIODevice *ioStream, QByteArray *buffer, bool *finished)
64 {
65 (*finished) = false;
66 memset((void*) buffer->data(), 0, buffer->size());
67 qint64 i = ioStream->read(buffer->data(), buffer_size);
68 buffer->resize(i);
69 (*finished) = true;
70 }
71
72
73 void QT_WIN_CALLBACK MediaObject::WaveOutCallBack(HWAVEOUT m_hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
74 {
75 Q_UNUSED(m_hWaveOut);
76 Q_UNUSED(dwInstance);
77 Q_UNUSED(dwParam2);
78
79 switch(uMsg)
80 {
81 case WOM_OPEN:
82 break;
83 case WOM_DONE:
84 {
85 WAVEHDR *waveHeader = (WAVEHDR*)dwParam1;
86 MediaObject* mediaObject = reinterpret_cast<MediaObject *>(waveHeader->dwUser);
87 if (mediaObject) {
88 mediaObject->swapBuffers();
89 }
90 }
91 break;
92 case WOM_CLOSE:
93 break;
94 }
95 }
96
97 class StreamReader : public Phonon::StreamInterface
98 {
99 public:
100 StreamReader(QObject *parent, const Phonon::MediaSource &source) :
101 m_seekable(false), m_pos(0), m_size(-1)
102 {
103 Q_UNUSED(parent);
104 connectToSource(source);
105 }
106
107 //for Phonon::StreamInterface
108 void writeData(const QByteArray &data)
109 {
110 QWriteLocker locker(&m_lock);
111 m_pos += data.size();
112 m_buffer += data;
113 }
114
115 void endOfData()
116 {
117 }
118
119 void setStreamSize(qint64 newSize)
120 {
121 QWriteLocker locker(&m_lock);
122 m_size = newSize;
123 }
124
125 qint64 streamSize() const
126 {
127 QReadLocker locker(&m_lock);
128 return m_size;
129 }
130
131 void setStreamSeekable(bool s)
132 {
133 QWriteLocker locker(&m_lock);
134 m_seekable = s;
135 }
136
137 bool streamSeekable() const
138 {
139 QReadLocker locker(&m_lock);
140 return m_seekable;
141 }
142
143 void setCurrentPos(qint64 pos)
144 {
145 QWriteLocker locker(&m_lock);
146 m_pos = pos;
147 seekStream(pos);
148 m_buffer.clear();
149 }
150
151 qint64 currentPos() const
152 {
153 QReadLocker locker(&m_lock);
154 return m_pos;
155 }
156
157 int currentBufferSize() const
158 {
159 QReadLocker locker(&m_lock);
160 return m_buffer.size();
161 }
162
163 //for Phonon::StreamInterface
164 QByteArray m_buffer;
165 bool m_seekable;
166 qint64 m_pos;
167 qint64 m_size;
168 mutable QReadWriteLock m_lock;
169 };
170
171 class IOWrapper : public QIODevice {
172 public:
173 IOWrapper(QObject *parent, const Phonon::MediaSource &source) : m_streamReader(this, source)
174 {
175 Q_UNUSED(parent);
176 setOpenMode(QIODevice::ReadOnly);
177 }
178 bool seek(qint64 pos);
179 qint64 size() const;
180 qint64 pos();
181 bool isReadable() const;
182 protected:
183 qint64 readData (char * data, qint64 maxSize);
184 qint64 writeData(const char *,qint64);
185 private:
186 StreamReader m_streamReader;
187 };
188
189 bool IOWrapper::isReadable () const
190 {
191 return true;
192 }
193
194 qint64 IOWrapper::pos()
195 {
196 return (m_streamReader.streamSeekable() ? m_streamReader.currentPos() : 0);
197 }
198
199 bool IOWrapper::seek( qint64 pos)
200 {
201 if (!m_streamReader.streamSeekable())
202 return false;
203 m_streamReader.setCurrentPos(pos);
204 return true;
205 }
206
207 qint64 IOWrapper::size() const
208 {
209 return m_streamReader.streamSize();
210 }
211
212 qint64 IOWrapper::readData(char * data, qint64 maxSize)
213 {
214 int oldSize = m_streamReader.currentBufferSize();
215 while (m_streamReader.currentBufferSize() < maxSize) {
216 m_streamReader.needData();
217 if (oldSize == m_streamReader.currentBufferSize()) {
218 break; //we didn't get any data
219 }
220 oldSize = m_streamReader.currentBufferSize();
221 }
222
223 qint64 bytesRead = qMin(qint64(m_streamReader.currentBufferSize()), maxSize);
224 {
225 QWriteLocker locker(&m_streamReader.m_lock);
226 qMemCopy(data, m_streamReader.m_buffer.data(), bytesRead);
227 //truncate the buffer
228 m_streamReader.m_buffer = m_streamReader.m_buffer.mid(bytesRead);
229 }
230 return bytesRead;
231 }
232
233 qint64 IOWrapper::writeData(const char *,qint64)
234 {
235 return 0;
236 }
237
238 MediaObject::MediaObject(QObject *parent) : m_file(0), m_stream(0),
239 m_hWaveOut(0), m_nextBufferIndex(1),
240 m_mediaSize(-1), m_bufferingFinished(0),
241 m_paused(0), m_tickInterval(0),
242 m_hasNextSource(0), m_hasSource(0),
243 m_sourceIsValid(0), m_errorType(Phonon::NoError),
244 m_currentTime(0), m_transitionTime(0),
245 m_tick(0), m_volume(100), m_prefinishMark(0),
246 m_tickIntervalResolution(0), m_bufferPrepared(0),
247 m_stopped(0)
248 {
249 m_thread = new WorkerThread();
250 connect(this, SIGNAL(outOfData(QIODevice*,QByteArray*,bool*)), m_thread, SLOT(stream(QIODevice*,QByteArray*,bool*)));
251 m_thread->start();
252 m_soundBuffer1.waveHeader = new WAVEHDR;
253 m_soundBuffer2.waveHeader = new WAVEHDR;
254 setParent(parent);
255 setState(Phonon::LoadingState);
256 }
257
258 MediaObject::~MediaObject()
259 {
260 stop();
261 disconnect(this, SIGNAL(outOfData(QIODevice*,QByteArray*,bool*)), m_thread, SLOT(stream(QIODevice*,QByteArray*,bool*)));
262 do { //The event loop of m_thread might not be started, yet
263 m_thread->quit(); //If the event loop is not started yet quit() does nothing
264 m_thread->wait(100);
265 } while (m_thread->isRunning());
266 delete m_thread;
267 deleteValidWaveOutDevice();
268 delete m_soundBuffer1.waveHeader;
269 delete m_soundBuffer2.waveHeader;
270 }
271
272 Phonon::State MediaObject::state() const
273 {
274 return m_state;
275 }
276
277 bool MediaObject::hasVideo() const
278 {
279 return false;
280 }
281
282 bool MediaObject::isSeekable() const
283 {
284 if (!m_stream)
285 return false;
286 return !m_stream->isSequential();
287 }
288
289 qint64 MediaObject::totalTime() const
290 {
291 return m_totalTime;
292 }
293
294 qint64 MediaObject::currentTime() const
295 {
296 //this handles inaccuracy when stopping on a title
297 return m_currentTime;
298 }
299
300 qint32 MediaObject::tickInterval() const
301 {
302 return m_tickInterval * m_tickIntervalResolution;
303 }
304
305 void MediaObject::setTickInterval(qint32 newTickInterval)
306 {
307 if ((m_tickIntervalResolution == 0) || (newTickInterval == 0))
308 return;
309 m_tickInterval = newTickInterval / m_tickIntervalResolution;
310 if ((newTickInterval > 0) && (m_tickInterval == 0))
311 m_tickInterval = 1;
312 }
313
314 void MediaObject::pause()
315 {
316 if (!m_paused) {
317 m_paused = true;
318 setState(Phonon::PausedState);
319 if (!(waveOutPause(m_hWaveOut) == MMSYSERR_NOERROR))
320 {
321 setError(Phonon::NormalError, QLatin1String("cannot pause (system error)"));
322 }
323 }
324 }
325
326 void MediaObject::stop()
327 {
328 setState(Phonon::StoppedState);
329 m_stopped = true;
330 m_paused = false;
331 seek(0);
332 if (!(waveOutReset(m_hWaveOut) == MMSYSERR_NOERROR))
333 setError(Phonon::NormalError, QLatin1String("cannot stop (system error)"));
334 }
335
336 void MediaObject::play()
337 {
338 if ((m_state == Phonon::PlayingState) && !m_paused && !m_stopped)
339 return;
340 if ((m_state == Phonon::LoadingState) ||
341 (m_state == Phonon::BufferingState) ||
342 (m_state == Phonon::ErrorState)) {
343 setError(Phonon::FatalError, QLatin1String("illegale state for playback"));
344 return;
345 }
346
347 if (m_state == Phonon::StoppedState)
348 stop();
349 if (m_sourceIsValid) {
350 setState(Phonon::PlayingState);
351 if (!m_paused) {
352 m_nextBufferIndex = true;
353 m_stopped = false;
354 playBuffer(m_soundBuffer1.waveHeader);
355 playBuffer(m_soundBuffer2.waveHeader);
356 } else {
357 if (!(waveOutRestart(m_hWaveOut) == MMSYSERR_NOERROR))
358 setError(Phonon::NormalError, QLatin1String("cannot resume (system)"));
359 }
360 } else {
361 setError(Phonon::FatalError, QLatin1String("cannot playback invalid source"));
362 }
363 m_paused = false;
364 }
365
366 QString MediaObject::errorString() const
367 {
368
369 return m_errorString;
370 }
371
372 Phonon::ErrorType MediaObject::errorType() const
373 {
374 return Phonon::ErrorType();
375 }
376
377 qint32 MediaObject::prefinishMark() const
378 {
379 return m_prefinishMark;
380 }
381
382 void MediaObject::setPrefinishMark(qint32 newPrefinishMark)
383 {
384 m_prefinishMark = newPrefinishMark;
385 }
386
387 qint32 MediaObject::transitionTime() const
388 {
389 return m_transitionTime;
390 }
391
392 void MediaObject::setTransitionTime(qint32 time)
393 {
394 m_transitionTime = time;
395 }
396
397 qint64 MediaObject::remainingTime() const
398 {
399 return m_totalTime - m_currentTime;
400 }
401
402 Phonon::MediaSource MediaObject::source() const
403 {
404 return Phonon::MediaSource();
405 }
406
407 void MediaObject::setNextSource(const Phonon::MediaSource &source)
408 {
409 m_nextSource = source;
410 m_hasNextSource = true;
411 }
412
413 void MediaObject::setSource(const Phonon::MediaSource &source)
414 {
415 if (m_state == Phonon::PlayingState)
416 {
417 setError(Phonon::NormalError, QLatin1String("source changed while playing"));
418 stop();
419 }
420
421 m_source = source;
422 m_hasSource = true;
423 m_sourceIsValid = false;
424
425 emit currentSourceChanged(source);
426
427 if (source.type() == Phonon::MediaSource::LocalFile) {
428 if (!openWaveFile(source.fileName())) {
429 setError(Phonon::FatalError, QLatin1String("cannot open media file"));
430 return ;
431 }
432 } else if (source.type() == Phonon::MediaSource::Stream) {
433 if (m_stream)
434 delete m_stream;
435 m_stream = new IOWrapper(this, source);
436 m_mediaSize = m_stream->size();
437 } else if (source.type() == Phonon::MediaSource::Url) {
438 if (!openWaveFile(source.url().toLocalFile())) {
439 setError(Phonon::FatalError, QLatin1String("cannot open media file"));
440 return ;
441 }
442 } else {
443 setError(Phonon::FatalError, QLatin1String("type of source not supported"));
444 return ;
445 }
446 setState(Phonon::LoadingState);
447
448 if (!readHeader())
449 setError(Phonon::FatalError, QLatin1String("invalid header"));
450 else if (!getWaveOutDevice())
451 setError(Phonon::FatalError, QLatin1String("No waveOut device available"));
452 else if (!fillBuffers())
453 setError(Phonon::FatalError, QLatin1String("no data for buffering"));
454 else if (!prepareBuffers())
455 setError(Phonon::FatalError, QLatin1String("cannot prepare buffers"));
456 else
457 m_sourceIsValid = true;
458
459 if (m_sourceIsValid)
460 setState(Phonon::StoppedState);
461 }
462
463 void MediaObject::seek(qint64 time)
464 {
465 if (!m_sourceIsValid) {
466 setError(Phonon::NormalError, QLatin1String("source is not valid"));
467 return;
468 }
469 if ((time >= 0) && (time < m_totalTime)) {
470 int counter = 0;
471 while (!m_bufferingFinished && (counter < 200)) {
472 Sleep(20);
473 counter ++;
474 }
475 if (counter >= 200) {
476 setError(Phonon::NormalError, QLatin1String("buffering timed out"));
477 return;
478 }
479
480 m_stream->seek(WAVEHEADER_SIZE + time * m_waveFormatEx.nSamplesPerSec * m_waveFormatEx.wBitsPerSample * m_waveFormatEx.nChannels / 8 / 1000);
481 m_currentTime = time;
482 if (m_state == Phonon::PlayingState)
483 play();
484 } else {
485 setError(Phonon::NormalError, QLatin1String("seeking out of range"));
486 }
487 }
488
489 void MediaObject::unPrepareBuffers()
490 {
491 if (m_bufferPrepared) {
492 DWORD err1 = waveOutUnprepareHeader(m_hWaveOut, m_soundBuffer1.waveHeader, sizeof(WAVEHDR));
493 DWORD err2 = waveOutUnprepareHeader(m_hWaveOut, m_soundBuffer2.waveHeader, sizeof(WAVEHDR));
494 if (!(err1 == MMSYSERR_NOERROR) || !(err2 == MMSYSERR_NOERROR))
495 setError(Phonon::NormalError, QLatin1String("cannot unprepare buffer") + getErrorText(err1) + getErrorText(err2));
496 }
497 m_bufferPrepared = false;
498 }
499
500 bool MediaObject::prepareBuffers()
501 {
502 memset((void*)m_soundBuffer1.waveHeader, 0, sizeof(WAVEHDR));
503 m_soundBuffer1.waveHeader->lpData = m_soundBuffer1.data.data();
504 m_soundBuffer1.waveHeader->dwBufferLength = m_soundBuffer1.data.size();
505 m_soundBuffer1.waveHeader->dwUser = (DWORD_PTR) this;
506
507 ZeroMemory((void*)m_soundBuffer2.waveHeader, sizeof(WAVEHDR));
508 m_soundBuffer2.waveHeader->lpData = m_soundBuffer2.data.data();
509 m_soundBuffer2.waveHeader->dwBufferLength = m_soundBuffer1.data.size();
510 m_soundBuffer2.waveHeader->dwUser = (DWORD_PTR) this;
511
512 m_bufferPrepared = (waveOutPrepareHeader(m_hWaveOut, m_soundBuffer1.waveHeader, sizeof(WAVEHDR)) == MMSYSERR_NOERROR)
513 && (waveOutPrepareHeader(m_hWaveOut, m_soundBuffer2.waveHeader, sizeof(WAVEHDR)) == MMSYSERR_NOERROR);
514 return m_bufferPrepared;
515 }
516
517 void MediaObject::deleteValidWaveOutDevice()
518 {
519 if (m_hWaveOut) {
520 unPrepareBuffers();
521 if (!(waveOutClose(m_hWaveOut) == MMSYSERR_NOERROR))
522 setError(Phonon::NormalError, QLatin1String("cannot close wave device"));
523 }
524 }
525
526 bool MediaObject::getWaveOutDevice()
527 {
528 deleteValidWaveOutDevice();
529
530 for(UINT deviceId = 0; deviceId < waveOutGetNumDevs(); deviceId++)
531 {
532 if(deviceId == waveOutGetNumDevs())
533 return false;
534 if(waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveFormatEx, (DWORD)WaveOutCallBack, 0, CALLBACK_FUNCTION) == MMSYSERR_NOERROR)
535 return m_hWaveOut; //m_hWaveOut !=0;
536 }
537 return false;
538 }
539
540 bool MediaObject::openWaveFile(QString fileName)
541 {
542 if (m_file)
543 delete m_file;
544 m_file = new QFile(fileName);
545 m_file->setParent(this);
546 m_stream = m_file;
547 m_mediaSize = m_file->size();
548 return (m_file->open(QIODevice::ReadOnly));
549 }
550
551 bool MediaObject::readHeader()
552 {
553 QByteArray header = m_stream->read(WAVEHEADER_SIZE);
554
555 if (header.size() == WAVEHEADER_SIZE) {
556
557 m_waveFormatEx.wFormatTag = *((WORD* )(header.data() + WAVEHEADER_OFFSET_FORMATTAG ));
558 m_waveFormatEx.nChannels = *((WORD* )(header.data() + WAVEHEADER_OFFSET_CHANNELS ));
559 m_waveFormatEx.nSamplesPerSec = *((DWORD*)(header.data() + WAVEHEADER_OFFSET_SAMPLESPERSEC ));
560 m_waveFormatEx.nAvgBytesPerSec = *((DWORD*)(header.data() + WAVEHEADER_OFFSET_AVGBYTESPERSEC));
561 m_waveFormatEx.nBlockAlign = *((WORD* )(header.data() + WAVEHEADER_OFFSET_BLOCKALIGN ));
562 m_waveFormatEx.wBitsPerSample = *((WORD* )(header.data() + WAVEHEADER_OFFSET_BITSPERSAMPLE ));
563
564 m_tickIntervalResolution = (qint64(buffer_size) * 8 * 1000) / m_waveFormatEx.nSamplesPerSec / m_waveFormatEx.wBitsPerSample / m_waveFormatEx.nChannels;
565 if (m_mediaSize > 0)
566 m_totalTime = ((m_mediaSize - WAVEHEADER_SIZE) * 8 * 1000) / m_waveFormatEx.nSamplesPerSec / m_waveFormatEx.wBitsPerSample / m_waveFormatEx.nChannels;
567 else
568 m_totalTime = -1;
569 emit totalTimeChanged(m_totalTime);
570 return true;
571 } else {
572 return false;
573 }
574 }
575
576 bool MediaObject::fillBuffers()
577 {
578
579 m_soundBuffer1.data = m_stream->read(buffer_size);
580 m_soundBuffer2.data = m_stream->read(buffer_size);
581
582 m_bufferingFinished = true;
583
584 if (!(m_soundBuffer1.data.size() > 0))
585 setError(Phonon::NormalError, QLatin1String("cannot read source"));
586 return true;
587 }
588
589 void MediaObject::setState(Phonon::State newState)
590 {
591 if (m_state == newState)
592 return;
593 emit stateChanged(newState, m_state);
594 m_state = newState;
595 }
596
597 void MediaObject::setError(ErrorType errorType, QString errorMessage)
598 {
599 m_errorType = errorType;
600 setState(Phonon::ErrorState);
601 m_errorString = errorMessage;
602 }
603
604 void MediaObject::setAudioOutput(QObject *audioOutput)
605 {
606 m_audioOutput = qobject_cast<AudioOutput*>(audioOutput);
607
608 if (m_audioOutput) {
609 m_volume = m_audioOutput->volume();
610 connect(m_audioOutput, SIGNAL(volumeChanged(qreal)), this, SLOT(setVolume(qreal)));
611 }
612 }
613
614 void MediaObject::setVolume(qreal newVolume)
615 {
616 m_volume = newVolume;
617 }
618
619 void MediaObject::swapBuffers()
620 {
621 if (m_stopped || m_paused)
622 return;
623
624 m_currentTime += m_tickIntervalResolution;
625 if (m_tickInterval) {
626 m_tick ++;
627 if (m_tick > (m_tickInterval - 1)) {
628 emit tick(m_currentTime);
629 m_tick = 0;
630 }
631 }
632 if ((m_prefinishMark > 0)&& (m_prefinishMark < m_currentTime))
633 emit prefinishMarkReached(m_totalTime - m_currentTime);
634
635 while (!m_bufferingFinished) {
636 setState(Phonon::BufferingState);
637 qWarning() << QLatin1String("buffer underun");
638 Sleep(20);
639 }
640
641 setState(Phonon::PlayingState);
642
643 //if size == o then stop...
644 if (m_nextBufferIndex) {
645 int size = m_soundBuffer1.waveHeader->dwBufferLength = m_soundBuffer1.data.size();
646 if (size == buffer_size) {
647 playBuffer(m_soundBuffer1.waveHeader);
648 emit outOfData(m_stream, &m_soundBuffer1.data, &m_bufferingFinished);
649 } else {
650 playBuffer(m_soundBuffer1.waveHeader);
651 m_stopped = true;
652 setState(Phonon::StoppedState);
653 emit finished();
654 seek(0);
655 }
656 } else {
657 int size = m_soundBuffer2.waveHeader->dwBufferLength = m_soundBuffer2.data.size();
658 if (size == buffer_size) {
659 playBuffer(m_soundBuffer2.waveHeader);
660 emit outOfData(m_stream, &m_soundBuffer2.data, &m_bufferingFinished);
661 } else {
662 playBuffer(m_soundBuffer2.waveHeader);
663 m_stopped = true;
664 setState(Phonon::StoppedState);
665 emit finished();
666 seek(0);
667 }
668 }
669 m_nextBufferIndex =! m_nextBufferIndex;
670 }
671
672
673 void MediaObject::playBuffer(WAVEHDR *waveHeader)
674 {
675 DWORD err = waveOutWrite(m_hWaveOut, waveHeader, sizeof(WAVEHDR));
676 if (!err == MMSYSERR_NOERROR) {
677 setError(Phonon::FatalError, QLatin1String("cannot play sound buffer (system) ") + getErrorText(err));
678 m_stopped = true;
679 }
680 }
681 }
682}
683
684QT_END_NAMESPACE
685
686#include "mediaobject.moc"
Note: See TracBrowser for help on using the repository browser.