/*************************************************************************** * Copyright (C) 2014-2017 by Ilya Kotov * * forkotov02@ya.ru * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "outputdirectsound.h" #define DS_BUFSIZE (128*1024) OutputDirectSound *OutputDirectSound::instance = 0; VolumeDirectSound *OutputDirectSound::volumeControl = 0; OutputDirectSound::DSoundChannels OutputDirectSound::m_dsound_pos[10] = { {Qmmp::CHAN_FRONT_LEFT, SPEAKER_FRONT_LEFT}, {Qmmp::CHAN_FRONT_RIGHT, SPEAKER_FRONT_RIGHT}, {Qmmp::CHAN_FRONT_CENTER, SPEAKER_FRONT_CENTER}, {Qmmp::CHAN_LFE, SPEAKER_LOW_FREQUENCY}, {Qmmp::CHAN_REAR_LEFT, SPEAKER_BACK_LEFT}, {Qmmp::CHAN_REAR_RIGHT, SPEAKER_BACK_RIGHT}, {Qmmp::CHAN_REAR_CENTER, SPEAKER_BACK_CENTER}, {Qmmp::CHAN_SIDE_LEFT, SPEAKER_SIDE_LEFT}, {Qmmp::CHAN_SIDE_RIGHT, SPEAKER_BACK_RIGHT}, {Qmmp::CHAN_NULL, 0} }; OutputDirectSound::OutputDirectSound() : Output() { m_ds = 0; m_primaryBuffer = 0; m_dsBuffer = 0; m_dsBufferAt = 0; m_latency = 0; m_bytesPerSecond = 0; m_reset = false; instance = this; } OutputDirectSound::~OutputDirectSound() { instance = 0; uninitialize(); } bool OutputDirectSound::initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format) { m_latency = 0; DSBUFFERDESC bufferDesc; HRESULT result = DirectSoundCreate8(0, &m_ds, 0); if(result != DS_OK) { qWarning("OutputDirectSound: DirectSoundCreate8 failed, error code = 0x%lx", result); m_ds = 0; return false; } if((result = m_ds->SetCooperativeLevel(GetDesktopWindow(), DSSCL_PRIORITY)) != DS_OK) { qWarning("OutputDirectSound: SetCooperativeLevel failed, error code = 0x%lx", result); return false; } ZeroMemory(&bufferDesc, sizeof(DSBUFFERDESC)); bufferDesc.dwSize = sizeof(DSBUFFERDESC); bufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME | DSBCAPS_LOCHARDWARE; bufferDesc.dwBufferBytes = 0; bufferDesc.lpwfxFormat = NULL; if((result = m_ds->CreateSoundBuffer(&bufferDesc, &m_primaryBuffer, NULL)) != DS_OK) { m_primaryBuffer = 0; qWarning("OutputDirectSound: CreateSoundBuffer failed, error code = 0x%lx", result); return false; } WAVEFORMATEXTENSIBLE wfex; wfex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfex.Format.nChannels = map.count(); wfex.Format.nSamplesPerSec = freq; wfex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE); if(format == Qmmp::PCM_S16LE) { wfex.Format.wBitsPerSample = 16; wfex.Samples.wValidBitsPerSample = 16; } else if(format == Qmmp::PCM_S24LE) { wfex.Format.wBitsPerSample = 32; wfex.Samples.wValidBitsPerSample = 24; } else if(format == Qmmp::PCM_S32LE) { wfex.Format.wBitsPerSample = 32; wfex.Samples.wValidBitsPerSample = 32; } else { format = Qmmp::PCM_S16LE; wfex.Format.wBitsPerSample = 16; wfex.Samples.wValidBitsPerSample = 16; } wfex.Format.nBlockAlign = (wfex.Format.wBitsPerSample / 8) * wfex.Format.nChannels; wfex.Format.nAvgBytesPerSec = wfex.Format.nSamplesPerSec * wfex.Format.nBlockAlign; //generate channel order ChannelMap out_map; int i = 0; DWORD mask = 0; while(m_dsound_pos[i].pos != Qmmp::CHAN_NULL) { if(map.contains(m_dsound_pos[i].pos)) { mask |= m_dsound_pos[i].chan_mask; out_map << m_dsound_pos[i].pos; } i++; } wfex.dwChannelMask = mask; wfex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; if((result = m_primaryBuffer->SetFormat((WAVEFORMATEX*)&wfex)) != DS_OK) { qWarning("OutputDirectSound: SetFormat failed, error code = 0x%lx", result); return false; } if((result = m_primaryBuffer->Play(0, 0, DSBPLAY_LOOPING)) != DS_OK) { qWarning("OutputDirectSound: Play failed, error code = 0x%lx", result); return false; } ZeroMemory(&bufferDesc, sizeof(DSBUFFERDESC)); bufferDesc.dwSize = sizeof(DSBUFFERDESC); bufferDesc.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY; bufferDesc.lpwfxFormat = (WAVEFORMATEX*)&wfex; bufferDesc.dwBufferBytes = DS_BUFSIZE; // buffer size IDirectSoundBuffer *pDSB; if((result = m_ds->CreateSoundBuffer(&bufferDesc, &pDSB, NULL)) != DS_OK) { qWarning("OutputDirectSound: CreateSoundBuffer failed, error code = 0x%lx", result); return false; } if((result = pDSB->QueryInterface(IID_IDirectSoundBuffer8, (void**)&m_dsBuffer)) != DS_OK) { m_dsBuffer = 0; qWarning("OutputDirectSound: QueryInterface failed, error code = 0x%lx", result); pDSB->Release(); return false; } m_dsBuffer->SetCurrentPosition(0); m_dsBuffer->Play(0,0,DSBPLAY_LOOPING); m_dsBufferAt = 0; configure(freq, out_map, format); if(volumeControl) volumeControl->restore(); m_bytesPerSecond = (sampleRate() * sampleSize() * channels()); return true; } qint64 OutputDirectSound::latency() { return m_latency; } qint64 OutputDirectSound::writeAudio(unsigned char *data, qint64 len) { unsigned char *ptr = 0, *ptr2 = 0; DWORD size = 0, size2 = 0; DWORD available = bytesToWrite(); //available bytes m_latency = (DS_BUFSIZE - available) * 1000 / m_bytesPerSecond; if(m_reset) { available = DS_BUFSIZE; m_dsBuffer->SetCurrentPosition(m_dsBufferAt); m_reset = false; } if(available < 128) { usleep(5000); return 0; } DWORD lockSize = qMin((DWORD)len, available); //required size HRESULT result = m_dsBuffer->Lock(m_dsBufferAt, lockSize, (void**)&ptr, (DWORD*)&size, (void**)&ptr2, (DWORD*)&size2, 0); if(result == DSERR_BUFFERLOST) { m_dsBuffer->Restore(); result = m_dsBuffer->Lock(m_dsBufferAt, lockSize, (void**)&ptr, (DWORD*)&size, (void**)&ptr2, (DWORD*)&size2, 0); } if(result != DS_OK) { qWarning("OutputDirectSound: unable to lock buffer, error = 0x%lx", result); return -1; } DWORD totalSize = size + size2; //total locked size if(format() == Qmmp::PCM_S24LE) { for(DWORD i = 0; i < totalSize / 4; ++i) { ((quint32*) data)[i] <<= 8; } } memmove(ptr, data, size); if(size2 > 0) memmove(ptr2, data + size, size2); m_dsBuffer->Unlock((void*)ptr, size, (void*)ptr2, size2); m_dsBufferAt += totalSize; m_dsBufferAt %= DS_BUFSIZE; return totalSize; } void OutputDirectSound::drain() { DWORD dsCurrentPlayCursor = 0; m_dsBuffer->GetCurrentPosition((DWORD*)&dsCurrentPlayCursor, 0); while(dsCurrentPlayCursor >= m_dsBufferAt) { m_dsBuffer->GetCurrentPosition((DWORD*)&dsCurrentPlayCursor, 0); } while (dsCurrentPlayCursor <= m_dsBufferAt) { m_dsBuffer->GetCurrentPosition((DWORD*)&dsCurrentPlayCursor, 0); } } void OutputDirectSound::suspend() { m_dsBuffer->Stop(); } void OutputDirectSound::resume() { HRESULT result = m_dsBuffer->Play(0,0,DSBPLAY_LOOPING); if(result == DSERR_BUFFERLOST) { result = m_dsBuffer->Play(0,0,DSBPLAY_LOOPING); m_dsBuffer->Restore(); } } void OutputDirectSound::reset() { m_reset = true; } IDirectSoundBuffer8 *OutputDirectSound::secondaryBuffer() { return m_dsBuffer; } void OutputDirectSound::uninitialize() { m_dsBufferAt = 0; if(m_dsBuffer) { m_dsBuffer->Stop(); m_dsBuffer->Release(); m_dsBuffer = 0; } if(m_primaryBuffer) { m_primaryBuffer->Stop(); m_primaryBuffer->Release(); m_primaryBuffer = 0; } if(m_ds) { m_ds->Release(); m_ds = 0; } } DWORD OutputDirectSound::bytesToWrite() { DWORD dsCurrentPlayCursor = 0; m_dsBuffer->GetCurrentPosition((DWORD*)&dsCurrentPlayCursor, 0); long available = dsCurrentPlayCursor - m_dsBufferAt; //available bytes if(available < 0) { available += DS_BUFSIZE; } return available; } /***** MIXER *****/ VolumeDirectSound::VolumeDirectSound() { OutputDirectSound::volumeControl = this; QSettings settings(Qmmp::configFile(), QSettings::IniFormat); m_volume.left = settings.value("DirectSound/left_volume", 100).toInt(); m_volume.right = settings.value("DirectSound/right_volume", 100).toInt(); } VolumeDirectSound::~VolumeDirectSound() { m_volume = volume(); QSettings settings(Qmmp::configFile(), QSettings::IniFormat); settings.setValue("DirectSound/left_volume", m_volume.left); settings.setValue("DirectSound/right_volume", m_volume.right); OutputDirectSound::volumeControl = 0; } void VolumeDirectSound::setVolume(const VolumeSettings &vol) { if(OutputDirectSound::instance && OutputDirectSound::instance->secondaryBuffer()) { int maxVol = qMax(vol.left, vol.right); double voldB = -100.0, pandB = 0; if(maxVol) { voldB = 20.0*log(maxVol/100.0)/log(10); int balance = (vol.right - vol.left)*100.0/maxVol; pandB = balance ? 20.0*log((100.0 - fabs(balance))/100.0)/log(10) : 0; if(balance > 0) pandB = -pandB; } OutputDirectSound::instance->secondaryBuffer()->SetVolume(voldB*100); OutputDirectSound::instance->secondaryBuffer()->SetPan(pandB*100); } m_volume = vol; } VolumeSettings VolumeDirectSound::volume() const { VolumeSettings vol; if(OutputDirectSound::instance && OutputDirectSound::instance->secondaryBuffer()) { long v = 0; double voldB = 0, pandB = 0; OutputDirectSound::instance->secondaryBuffer()->GetVolume(&v); voldB = v / 100.0; OutputDirectSound::instance->secondaryBuffer()->GetPan(&v); pandB = v / 100.0; int volume = 100*pow(10, voldB/20.0); int balance = 100 - 100*pow(10, abs(pandB)/20.0); if(pandB > 0) balance = -balance; vol.left = volume-qMax(balance,0)*volume/100.0; vol.right = volume+qMin(balance,0)*volume/100.0; return vol; } return m_volume; } void VolumeDirectSound::restore() { setVolume(m_volume); }