diff -r 000000000000 -r 1918ee327afb src/3rdparty/phonon/waveout/mediaobject.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/3rdparty/phonon/waveout/mediaobject.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,686 @@ +/* This file is part of the KDE project. + +Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + +This library is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 2.1 or 3 of the License. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library. If not, see . +*/ + +#include "mediaobject.h" +#include "audiooutput.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define WAVEHEADER_OFFSET_FORMATTAG 20 +#define WAVEHEADER_OFFSET_CHANNELS 22 +#define WAVEHEADER_OFFSET_SAMPLESPERSEC 24 +#define WAVEHEADER_OFFSET_AVGBYTESPERSEC 28 +#define WAVEHEADER_OFFSET_BLOCKALIGN 32 +#define WAVEHEADER_OFFSET_BITSPERSAMPLE 34 +#define WAVEHEADER_OFFSET_DATA 44 +#define WAVEHEADER_SIZE WAVEHEADER_OFFSET_DATA + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ + namespace WaveOut + { + static unsigned int buffer_size = (16 * 1024 * 4); + + QString getErrorText(MMRESULT error) + { + ushort b[256]; + waveOutGetErrorText(error, (LPWSTR)b, 256); + return QString::fromUtf16(b); + } + + class WorkerThread : public QThread + { + Q_OBJECT + public slots: + void stream(QIODevice *file, QByteArray *buffer, bool *finished); + }; + + void WorkerThread::stream(QIODevice *ioStream, QByteArray *buffer, bool *finished) + { + (*finished) = false; + memset((void*) buffer->data(), 0, buffer->size()); + qint64 i = ioStream->read(buffer->data(), buffer_size); + buffer->resize(i); + (*finished) = true; + } + + + void CALLBACK MediaObject::WaveOutCallBack(HWAVEOUT m_hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) + { + Q_UNUSED(m_hWaveOut); + Q_UNUSED(dwInstance); + Q_UNUSED(dwParam2); + + switch(uMsg) + { + case WOM_OPEN: + break; + case WOM_DONE: + { + WAVEHDR *waveHeader = (WAVEHDR*)dwParam1; + MediaObject* mediaObject = reinterpret_cast(waveHeader->dwUser); + if (mediaObject) { + mediaObject->swapBuffers(); + } + } + break; + case WOM_CLOSE: + break; + } + } + + class StreamReader : public Phonon::StreamInterface + { + public: + StreamReader(QObject *parent, const Phonon::MediaSource &source) : + m_seekable(false), m_pos(0), m_size(-1) + { + Q_UNUSED(parent); + connectToSource(source); + } + + //for Phonon::StreamInterface + void writeData(const QByteArray &data) + { + QWriteLocker locker(&m_lock); + m_pos += data.size(); + m_buffer += data; + } + + void endOfData() + { + } + + void setStreamSize(qint64 newSize) + { + QWriteLocker locker(&m_lock); + m_size = newSize; + } + + qint64 streamSize() const + { + QReadLocker locker(&m_lock); + return m_size; + } + + void setStreamSeekable(bool s) + { + QWriteLocker locker(&m_lock); + m_seekable = s; + } + + bool streamSeekable() const + { + QReadLocker locker(&m_lock); + return m_seekable; + } + + void setCurrentPos(qint64 pos) + { + QWriteLocker locker(&m_lock); + m_pos = pos; + seekStream(pos); + m_buffer.clear(); + } + + qint64 currentPos() const + { + QReadLocker locker(&m_lock); + return m_pos; + } + + int currentBufferSize() const + { + QReadLocker locker(&m_lock); + return m_buffer.size(); + } + + //for Phonon::StreamInterface + QByteArray m_buffer; + bool m_seekable; + qint64 m_pos; + qint64 m_size; + mutable QReadWriteLock m_lock; + }; + + class IOWrapper : public QIODevice { + public: + IOWrapper(QObject *parent, const Phonon::MediaSource &source) : m_streamReader(this, source) + { + Q_UNUSED(parent); + setOpenMode(QIODevice::ReadOnly); + } + bool seek(qint64 pos); + qint64 size() const; + qint64 pos(); + bool isReadable() const; + protected: + qint64 readData (char * data, qint64 maxSize); + qint64 writeData(const char *,qint64); + private: + StreamReader m_streamReader; + }; + + bool IOWrapper::isReadable () const + { + return true; + } + + qint64 IOWrapper::pos() + { + return (m_streamReader.streamSeekable() ? m_streamReader.currentPos() : 0); + } + + bool IOWrapper::seek( qint64 pos) + { + if (!m_streamReader.streamSeekable()) + return false; + m_streamReader.setCurrentPos(pos); + return true; + } + + qint64 IOWrapper::size() const + { + return m_streamReader.streamSize(); + } + + qint64 IOWrapper::readData(char * data, qint64 maxSize) + { + int oldSize = m_streamReader.currentBufferSize(); + while (m_streamReader.currentBufferSize() < maxSize) { + m_streamReader.needData(); + if (oldSize == m_streamReader.currentBufferSize()) { + break; //we didn't get any data + } + oldSize = m_streamReader.currentBufferSize(); + } + + qint64 bytesRead = qMin(qint64(m_streamReader.currentBufferSize()), maxSize); + { + QWriteLocker locker(&m_streamReader.m_lock); + qMemCopy(data, m_streamReader.m_buffer.data(), bytesRead); + //truncate the buffer + m_streamReader.m_buffer = m_streamReader.m_buffer.mid(bytesRead); + } + return bytesRead; + } + + qint64 IOWrapper::writeData(const char *,qint64) + { + return 0; + } + + MediaObject::MediaObject(QObject *parent) : m_file(0), m_stream(0), + m_hWaveOut(0), m_nextBufferIndex(1), + m_mediaSize(-1), m_bufferingFinished(0), + m_paused(0), m_tickInterval(0), + m_hasNextSource(0), m_hasSource(0), + m_sourceIsValid(0), m_errorType(Phonon::NoError), + m_currentTime(0), m_transitionTime(0), + m_tick(0), m_volume(100), m_prefinishMark(0), + m_tickIntervalResolution(0), m_bufferPrepared(0), + m_stopped(0) + { + m_thread = new WorkerThread(); + connect(this, SIGNAL(outOfData(QIODevice*, QByteArray*, bool*)), m_thread, SLOT(stream(QIODevice*, QByteArray*, bool*))); + m_thread->start(); + m_soundBuffer1.waveHeader = new WAVEHDR; + m_soundBuffer2.waveHeader = new WAVEHDR; + setParent(parent); + setState(Phonon::LoadingState); + } + + MediaObject::~MediaObject() + { + stop(); + disconnect(this, SIGNAL(outOfData(QIODevice*, QByteArray*, bool*)), m_thread, SLOT(stream(QIODevice*, QByteArray*, bool*))); + do { //The event loop of m_thread might not be started, yet + m_thread->quit(); //If the event loop is not started yet quit() does nothing + m_thread->wait(100); + } while (m_thread->isRunning()); + delete m_thread; + deleteValidWaveOutDevice(); + delete m_soundBuffer1.waveHeader; + delete m_soundBuffer2.waveHeader; + } + + Phonon::State MediaObject::state() const + { + return m_state; + } + + bool MediaObject::hasVideo() const + { + return false; + } + + bool MediaObject::isSeekable() const + { + if (!m_stream) + return false; + return !m_stream->isSequential(); + } + + qint64 MediaObject::totalTime() const + { + return m_totalTime; + } + + qint64 MediaObject::currentTime() const + { + //this handles inaccuracy when stopping on a title + return m_currentTime; + } + + qint32 MediaObject::tickInterval() const + { + return m_tickInterval * m_tickIntervalResolution; + } + + void MediaObject::setTickInterval(qint32 newTickInterval) + { + if ((m_tickIntervalResolution == 0) || (newTickInterval == 0)) + return; + m_tickInterval = newTickInterval / m_tickIntervalResolution; + if ((newTickInterval > 0) && (m_tickInterval == 0)) + m_tickInterval = 1; + } + + void MediaObject::pause() + { + if (!m_paused) { + m_paused = true; + setState(Phonon::PausedState); + if (!(waveOutPause(m_hWaveOut) == MMSYSERR_NOERROR)) + { + setError(Phonon::NormalError, QLatin1String("cannot pause (system error)")); + } + } + } + + void MediaObject::stop() + { + setState(Phonon::StoppedState); + m_stopped = true; + m_paused = false; + seek(0); + if (!(waveOutReset(m_hWaveOut) == MMSYSERR_NOERROR)) + setError(Phonon::NormalError, QLatin1String("cannot stop (system error)")); + } + + void MediaObject::play() + { + if ((m_state == Phonon::PlayingState) && !m_paused && !m_stopped) + return; + if ((m_state == Phonon::LoadingState) || + (m_state == Phonon::BufferingState) || + (m_state == Phonon::ErrorState)) { + setError(Phonon::FatalError, QLatin1String("illegale state for playback")); + return; + } + + if (m_state == Phonon::StoppedState) + stop(); + if (m_sourceIsValid) { + setState(Phonon::PlayingState); + if (!m_paused) { + m_nextBufferIndex = true; + m_stopped = false; + playBuffer(m_soundBuffer1.waveHeader); + playBuffer(m_soundBuffer2.waveHeader); + } else { + if (!(waveOutRestart(m_hWaveOut) == MMSYSERR_NOERROR)) + setError(Phonon::NormalError, QLatin1String("cannot resume (system)")); + } + } else { + setError(Phonon::FatalError, QLatin1String("cannot playback invalid source")); + } + m_paused = false; + } + + QString MediaObject::errorString() const + { + + return m_errorString; + } + + Phonon::ErrorType MediaObject::errorType() const + { + return Phonon::ErrorType(); + } + + qint32 MediaObject::prefinishMark() const + { + return m_prefinishMark; + } + + void MediaObject::setPrefinishMark(qint32 newPrefinishMark) + { + m_prefinishMark = newPrefinishMark; + } + + qint32 MediaObject::transitionTime() const + { + return m_transitionTime; + } + + void MediaObject::setTransitionTime(qint32 time) + { + m_transitionTime = time; + } + + qint64 MediaObject::remainingTime() const + { + return m_totalTime - m_currentTime; + } + + Phonon::MediaSource MediaObject::source() const + { + return Phonon::MediaSource(); + } + + void MediaObject::setNextSource(const Phonon::MediaSource &source) + { + m_nextSource = source; + m_hasNextSource = true; + } + + void MediaObject::setSource(const Phonon::MediaSource &source) + { + if (m_state == Phonon::PlayingState) + { + setError(Phonon::NormalError, QLatin1String("source changed while playing")); + stop(); + } + + m_source = source; + m_hasSource = true; + m_sourceIsValid = false; + + emit currentSourceChanged(source); + + if (source.type() == Phonon::MediaSource::LocalFile) { + if (!openWaveFile(source.fileName())) { + setError(Phonon::FatalError, QLatin1String("cannot open media file")); + return ; + } + } else if (source.type() == Phonon::MediaSource::Stream) { + if (m_stream) + delete m_stream; + m_stream = new IOWrapper(this, source); + m_mediaSize = m_stream->size(); + } else if (source.type() == Phonon::MediaSource::Url) { + if (!openWaveFile(source.url().toLocalFile())) { + setError(Phonon::FatalError, QLatin1String("cannot open media file")); + return ; + } + } else { + setError(Phonon::FatalError, QLatin1String("type of source not supported")); + return ; + } + setState(Phonon::LoadingState); + + if (!readHeader()) + setError(Phonon::FatalError, QLatin1String("invalid header")); + else if (!getWaveOutDevice()) + setError(Phonon::FatalError, QLatin1String("No waveOut device available")); + else if (!fillBuffers()) + setError(Phonon::FatalError, QLatin1String("no data for buffering")); + else if (!prepareBuffers()) + setError(Phonon::FatalError, QLatin1String("cannot prepare buffers")); + else + m_sourceIsValid = true; + + if (m_sourceIsValid) + setState(Phonon::StoppedState); + } + + void MediaObject::seek(qint64 time) + { + if (!m_sourceIsValid) { + setError(Phonon::NormalError, QLatin1String("source is not valid")); + return; + } + if ((time >= 0) && (time < m_totalTime)) { + int counter = 0; + while (!m_bufferingFinished && (counter < 200)) { + Sleep(20); + counter ++; + } + if (counter >= 200) { + setError(Phonon::NormalError, QLatin1String("buffering timed out")); + return; + } + + m_stream->seek(WAVEHEADER_SIZE + time * m_waveFormatEx.nSamplesPerSec * m_waveFormatEx.wBitsPerSample * m_waveFormatEx.nChannels / 8 / 1000); + m_currentTime = time; + if (m_state == Phonon::PlayingState) + play(); + } else { + setError(Phonon::NormalError, QLatin1String("seeking out of range")); + } + } + + void MediaObject::unPrepareBuffers() + { + if (m_bufferPrepared) { + DWORD err1 = waveOutUnprepareHeader(m_hWaveOut, m_soundBuffer1.waveHeader, sizeof(WAVEHDR)); + DWORD err2 = waveOutUnprepareHeader(m_hWaveOut, m_soundBuffer2.waveHeader, sizeof(WAVEHDR)); + if (!(err1 == MMSYSERR_NOERROR) || !(err2 == MMSYSERR_NOERROR)) + setError(Phonon::NormalError, QLatin1String("cannot unprepare buffer") + getErrorText(err1) + getErrorText(err2)); + } + m_bufferPrepared = false; + } + + bool MediaObject::prepareBuffers() + { + memset((void*)m_soundBuffer1.waveHeader, 0, sizeof(WAVEHDR)); + m_soundBuffer1.waveHeader->lpData = m_soundBuffer1.data.data(); + m_soundBuffer1.waveHeader->dwBufferLength = m_soundBuffer1.data.size(); + m_soundBuffer1.waveHeader->dwUser = (DWORD_PTR) this; + + ZeroMemory((void*)m_soundBuffer2.waveHeader, sizeof(WAVEHDR)); + m_soundBuffer2.waveHeader->lpData = m_soundBuffer2.data.data(); + m_soundBuffer2.waveHeader->dwBufferLength = m_soundBuffer1.data.size(); + m_soundBuffer2.waveHeader->dwUser = (DWORD_PTR) this; + + m_bufferPrepared = (waveOutPrepareHeader(m_hWaveOut, m_soundBuffer1.waveHeader, sizeof(WAVEHDR)) == MMSYSERR_NOERROR) + && (waveOutPrepareHeader(m_hWaveOut, m_soundBuffer2.waveHeader, sizeof(WAVEHDR)) == MMSYSERR_NOERROR); + return m_bufferPrepared; + } + + void MediaObject::deleteValidWaveOutDevice() + { + if (m_hWaveOut) { + unPrepareBuffers(); + if (!(waveOutClose(m_hWaveOut) == MMSYSERR_NOERROR)) + setError(Phonon::NormalError, QLatin1String("cannot close wave device")); + } + } + + bool MediaObject::getWaveOutDevice() + { + deleteValidWaveOutDevice(); + + for(UINT deviceId = 0; deviceId < waveOutGetNumDevs(); deviceId++) + { + if(deviceId == waveOutGetNumDevs()) + return false; + if(waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveFormatEx, (DWORD)WaveOutCallBack, 0, CALLBACK_FUNCTION) == MMSYSERR_NOERROR) + return m_hWaveOut; //m_hWaveOut !=0; + } + return false; + } + + bool MediaObject::openWaveFile(QString fileName) + { + if (m_file) + delete m_file; + m_file = new QFile(fileName); + m_file->setParent(this); + m_stream = m_file; + m_mediaSize = m_file->size(); + return (m_file->open(QIODevice::ReadOnly)); + } + + bool MediaObject::readHeader() + { + QByteArray header = m_stream->read(WAVEHEADER_SIZE); + + if (header.size() == WAVEHEADER_SIZE) { + + m_waveFormatEx.wFormatTag = *((WORD* )(header.data() + WAVEHEADER_OFFSET_FORMATTAG )); + m_waveFormatEx.nChannels = *((WORD* )(header.data() + WAVEHEADER_OFFSET_CHANNELS )); + m_waveFormatEx.nSamplesPerSec = *((DWORD*)(header.data() + WAVEHEADER_OFFSET_SAMPLESPERSEC )); + m_waveFormatEx.nAvgBytesPerSec = *((DWORD*)(header.data() + WAVEHEADER_OFFSET_AVGBYTESPERSEC)); + m_waveFormatEx.nBlockAlign = *((WORD* )(header.data() + WAVEHEADER_OFFSET_BLOCKALIGN )); + m_waveFormatEx.wBitsPerSample = *((WORD* )(header.data() + WAVEHEADER_OFFSET_BITSPERSAMPLE )); + + m_tickIntervalResolution = (qint64(buffer_size) * 8 * 1000) / m_waveFormatEx.nSamplesPerSec / m_waveFormatEx.wBitsPerSample / m_waveFormatEx.nChannels; + if (m_mediaSize > 0) + m_totalTime = ((m_mediaSize - WAVEHEADER_SIZE) * 8 * 1000) / m_waveFormatEx.nSamplesPerSec / m_waveFormatEx.wBitsPerSample / m_waveFormatEx.nChannels; + else + m_totalTime = -1; + emit totalTimeChanged(m_totalTime); + return true; + } else { + return false; + } + } + + bool MediaObject::fillBuffers() + { + + m_soundBuffer1.data = m_stream->read(buffer_size); + m_soundBuffer2.data = m_stream->read(buffer_size); + + m_bufferingFinished = true; + + if (!(m_soundBuffer1.data.size() > 0)) + setError(Phonon::NormalError, QLatin1String("cannot read source")); + return true; + } + + void MediaObject::setState(Phonon::State newState) + { + if (m_state == newState) + return; + emit stateChanged(newState, m_state); + m_state = newState; + } + + void MediaObject::setError(ErrorType errorType, QString errorMessage) + { + m_errorType = errorType; + setState(Phonon::ErrorState); + m_errorString = errorMessage; + } + + void MediaObject::setAudioOutput(QObject *audioOutput) + { + m_audioOutput = qobject_cast(audioOutput); + + if (m_audioOutput) { + m_volume = m_audioOutput->volume(); + connect(m_audioOutput, SIGNAL(volumeChanged(qreal)), this, SLOT(setVolume(qreal))); + } + } + + void MediaObject::setVolume(qreal newVolume) + { + m_volume = newVolume; + } + + void MediaObject::swapBuffers() + { + if (m_stopped || m_paused) + return; + + m_currentTime += m_tickIntervalResolution; + if (m_tickInterval) { + m_tick ++; + if (m_tick > (m_tickInterval - 1)) { + emit tick(m_currentTime); + m_tick = 0; + } + } + if ((m_prefinishMark > 0)&& (m_prefinishMark < m_currentTime)) + emit prefinishMarkReached(m_totalTime - m_currentTime); + + while (!m_bufferingFinished) { + setState(Phonon::BufferingState); + qWarning() << QLatin1String("buffer underun"); + Sleep(20); + } + + setState(Phonon::PlayingState); + + //if size == o then stop... + if (m_nextBufferIndex) { + int size = m_soundBuffer1.waveHeader->dwBufferLength = m_soundBuffer1.data.size(); + if (size == buffer_size) { + playBuffer(m_soundBuffer1.waveHeader); + emit outOfData(m_stream, &m_soundBuffer1.data, &m_bufferingFinished); + } else { + playBuffer(m_soundBuffer1.waveHeader); + m_stopped = true; + setState(Phonon::StoppedState); + emit finished(); + seek(0); + } + } else { + int size = m_soundBuffer2.waveHeader->dwBufferLength = m_soundBuffer2.data.size(); + if (size == buffer_size) { + playBuffer(m_soundBuffer2.waveHeader); + emit outOfData(m_stream, &m_soundBuffer2.data, &m_bufferingFinished); + } else { + playBuffer(m_soundBuffer2.waveHeader); + m_stopped = true; + setState(Phonon::StoppedState); + emit finished(); + seek(0); + } + } + m_nextBufferIndex =! m_nextBufferIndex; + } + + + void MediaObject::playBuffer(WAVEHDR *waveHeader) + { + DWORD err = waveOutWrite(m_hWaveOut, waveHeader, sizeof(WAVEHDR)); + if (!err == MMSYSERR_NOERROR) { + setError(Phonon::FatalError, QLatin1String("cannot play sound buffer (system) ") + getErrorText(err)); + m_stopped = true; + } + } + } +} + +QT_END_NAMESPACE + +#include "mediaobject.moc"