1 /***************************************************************************
2  *   Copyright (C) 2014-2021 by Ilya Kotov                                 *
3  *   forkotov02@ya.ru                                                      *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20 
21 #include <QObject>
22 #include <QSettings>
23 #include <QMessageBox>
24 #include <string.h>
25 #include <iostream>
26 #include <unistd.h>
27 #include <qmmp/buffer.h>
28 #include <math.h>
29 #include "outputdirectsound.h"
30 
31 #define DS_BUFSIZE (128*1024)
32 
33 OutputDirectSound *OutputDirectSound::instance = nullptr;
34 VolumeDirectSound *OutputDirectSound::volumeControl = nullptr;
35 OutputDirectSound::DSoundChannels OutputDirectSound::m_dsound_pos[10]  = {
36    {Qmmp::CHAN_FRONT_LEFT, SPEAKER_FRONT_LEFT},
37    {Qmmp::CHAN_FRONT_RIGHT, SPEAKER_FRONT_RIGHT},
38    {Qmmp::CHAN_FRONT_CENTER, SPEAKER_FRONT_CENTER},
39    {Qmmp::CHAN_LFE, SPEAKER_LOW_FREQUENCY},
40    {Qmmp::CHAN_REAR_LEFT, SPEAKER_BACK_LEFT},
41    {Qmmp::CHAN_REAR_RIGHT, SPEAKER_BACK_RIGHT},
42    {Qmmp::CHAN_REAR_CENTER, SPEAKER_BACK_CENTER},
43    {Qmmp::CHAN_SIDE_LEFT, SPEAKER_SIDE_LEFT},
44    {Qmmp::CHAN_SIDE_RIGHT, SPEAKER_BACK_RIGHT},
45    {Qmmp::CHAN_NULL, 0}
46 };
47 
OutputDirectSound()48 OutputDirectSound::OutputDirectSound() : Output()
49 {
50     instance = this;
51 }
52 
~OutputDirectSound()53 OutputDirectSound::~OutputDirectSound()
54 {
55     instance = nullptr;
56     uninitialize();
57 }
58 
initialize(quint32 freq,ChannelMap map,Qmmp::AudioFormat format)59 bool OutputDirectSound::initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format)
60 {
61     m_latency = 0;
62     DSBUFFERDESC bufferDesc;
63 
64 
65     HRESULT result = DirectSoundCreate8(nullptr, &m_ds, nullptr);
66     if(result != DS_OK)
67     {
68         qWarning("OutputDirectSound: DirectSoundCreate8 failed, error code = 0x%lx", result);
69         m_ds = nullptr;
70         return false;
71     }
72 
73     if((result = m_ds->SetCooperativeLevel(GetDesktopWindow(), DSSCL_PRIORITY)) != DS_OK)
74     {
75         qWarning("OutputDirectSound: SetCooperativeLevel failed, error code = 0x%lx", result);
76         return false;
77     }
78 
79     ZeroMemory(&bufferDesc, sizeof(DSBUFFERDESC));
80     bufferDesc.dwSize        = sizeof(DSBUFFERDESC);
81     bufferDesc.dwFlags       = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME | DSBCAPS_LOCHARDWARE;
82     bufferDesc.dwBufferBytes = 0;
83     bufferDesc.lpwfxFormat   = NULL;
84 
85     if((result = m_ds->CreateSoundBuffer(&bufferDesc, &m_primaryBuffer, NULL)) != DS_OK)
86     {
87         m_primaryBuffer = nullptr;
88         qWarning("OutputDirectSound: CreateSoundBuffer failed, error code = 0x%lx", result);
89         return false;
90     }
91 
92     WAVEFORMATEXTENSIBLE wfex;
93     wfex.Format.wFormatTag      = WAVE_FORMAT_EXTENSIBLE;
94     wfex.Format.nChannels       = map.count();
95     wfex.Format.nSamplesPerSec  = freq;
96     wfex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE);
97 
98     if(format == Qmmp::PCM_S16LE)
99     {
100         wfex.Format.wBitsPerSample = 16;
101         wfex.Samples.wValidBitsPerSample = 16;
102     }
103     else if(format == Qmmp::PCM_S24LE)
104     {
105         wfex.Format.wBitsPerSample  = 32;
106         wfex.Samples.wValidBitsPerSample = 24;
107     }
108     else if(format == Qmmp::PCM_S32LE)
109     {
110         wfex.Format.wBitsPerSample  = 32;
111         wfex.Samples.wValidBitsPerSample = 32;
112     }
113     else
114     {
115         format = Qmmp::PCM_S16LE;
116         wfex.Format.wBitsPerSample  = 16;
117         wfex.Samples.wValidBitsPerSample = 16;
118     }
119 
120     wfex.Format.nBlockAlign     = (wfex.Format.wBitsPerSample / 8) * wfex.Format.nChannels;
121     wfex.Format.nAvgBytesPerSec = wfex.Format.nSamplesPerSec * wfex.Format.nBlockAlign;
122 
123     //generate channel order
124     ChannelMap out_map;
125     int i = 0;
126     DWORD mask = 0;
127     while(m_dsound_pos[i].pos != Qmmp::CHAN_NULL)
128     {
129         if(map.contains(m_dsound_pos[i].pos))
130         {
131             mask |= m_dsound_pos[i].chan_mask;
132             out_map << m_dsound_pos[i].pos;
133         }
134         i++;
135     }
136 
137     wfex.dwChannelMask = mask;
138     wfex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
139 
140     if((result = m_primaryBuffer->SetFormat((WAVEFORMATEX*)&wfex)) != DS_OK)
141     {
142         qWarning("OutputDirectSound: SetFormat failed, error code = 0x%lx", result);
143         return false;
144     }
145 
146     if((result = m_primaryBuffer->Play(0, 0, DSBPLAY_LOOPING)) != DS_OK)
147     {
148         qWarning("OutputDirectSound: Play failed, error code = 0x%lx", result);
149         return false;
150     }
151 
152     ZeroMemory(&bufferDesc, sizeof(DSBUFFERDESC));
153     bufferDesc.dwSize        = sizeof(DSBUFFERDESC);
154     bufferDesc.dwFlags       = DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME |
155             DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY;
156     bufferDesc.lpwfxFormat   = (WAVEFORMATEX*)&wfex;
157     bufferDesc.dwBufferBytes = DS_BUFSIZE; // buffer size
158 
159     IDirectSoundBuffer *pDSB;
160     if((result = m_ds->CreateSoundBuffer(&bufferDesc, &pDSB, NULL)) != DS_OK)
161     {
162         qWarning("OutputDirectSound: CreateSoundBuffer failed, error code = 0x%lx", result);
163         return false;
164     }
165 
166     if((result = pDSB->QueryInterface(IID_IDirectSoundBuffer8, (void**)&m_dsBuffer)) != DS_OK)
167     {
168         m_dsBuffer = nullptr;
169         qWarning("OutputDirectSound: QueryInterface failed, error code = 0x%lx", result);
170         pDSB->Release();
171         return false;
172     }
173 
174     m_dsBuffer->SetCurrentPosition(0);
175     m_dsBuffer->Play(0,0,DSBPLAY_LOOPING);
176     m_dsBufferAt = 0;
177     configure(freq, out_map, format);
178     if(volumeControl)
179         volumeControl->restore();
180     m_bytesPerSecond = (sampleRate() * sampleSize() * channels());
181     return true;
182 }
183 
latency()184 qint64 OutputDirectSound::latency()
185 {
186     return m_latency;
187 }
188 
writeAudio(unsigned char * data,qint64 len)189 qint64 OutputDirectSound::writeAudio(unsigned char *data, qint64 len)
190 {
191     unsigned char *ptr = nullptr, *ptr2 = nullptr;
192     DWORD size = 0, size2 = 0;
193     DWORD available = bytesToWrite(); //available bytes
194     m_latency = (DS_BUFSIZE - available) * 1000 / m_bytesPerSecond;
195 
196     if(m_reset)
197     {
198         available = DS_BUFSIZE;
199         m_dsBuffer->SetCurrentPosition(m_dsBufferAt);
200         m_reset = false;
201     }
202 
203     if(available < 128)
204     {
205         usleep(5000);
206         return 0;
207     }
208     DWORD lockSize = qMin((DWORD)len, available); //required size
209 
210     HRESULT result = m_dsBuffer->Lock(m_dsBufferAt, lockSize,
211                                       (void**)&ptr, (DWORD*)&size,
212                                       (void**)&ptr2, (DWORD*)&size2, 0);
213     if(result == DSERR_BUFFERLOST)
214     {
215         m_dsBuffer->Restore();
216         result = m_dsBuffer->Lock(m_dsBufferAt, lockSize,
217                                   (void**)&ptr, (DWORD*)&size,
218                                   (void**)&ptr2, (DWORD*)&size2, 0);
219     }
220     if(result != DS_OK)
221     {
222         qWarning("OutputDirectSound: unable to lock buffer, error = 0x%lx", result);
223         return -1;
224     }
225 
226     DWORD totalSize = size + size2; //total locked size
227 
228     if(format() == Qmmp::PCM_S24LE)
229     {
230         for(DWORD i = 0; i < totalSize / 4; ++i)
231         {
232             ((quint32*) data)[i] <<= 8;
233         }
234     }
235 
236     memmove(ptr, data, size);
237     if(size2 > 0)
238         memmove(ptr2, data + size, size2);
239 
240     m_dsBuffer->Unlock((void*)ptr, size, (void*)ptr2, size2);
241 
242     m_dsBufferAt += totalSize;
243     m_dsBufferAt %= DS_BUFSIZE;
244 
245     return totalSize;
246 }
247 
drain()248 void OutputDirectSound::drain()
249 {
250     DWORD dsCurrentPlayCursor = 0;
251     m_dsBuffer->GetCurrentPosition((DWORD*)&dsCurrentPlayCursor, nullptr);
252 
253     while(dsCurrentPlayCursor >= m_dsBufferAt)
254     {
255         m_dsBuffer->GetCurrentPosition((DWORD*)&dsCurrentPlayCursor, nullptr);
256     }
257     while (dsCurrentPlayCursor <= m_dsBufferAt)
258     {
259         m_dsBuffer->GetCurrentPosition((DWORD*)&dsCurrentPlayCursor, nullptr);
260     }
261 }
262 
suspend()263 void OutputDirectSound::suspend()
264 {
265     m_dsBuffer->Stop();
266 }
267 
resume()268 void OutputDirectSound::resume()
269 {
270     HRESULT result = m_dsBuffer->Play(0,0,DSBPLAY_LOOPING);
271     if(result == DSERR_BUFFERLOST)
272     {
273         m_dsBuffer->Play(0,0,DSBPLAY_LOOPING);
274         m_dsBuffer->Restore();
275     }
276 }
277 
reset()278 void OutputDirectSound::reset()
279 {
280     m_reset = true;
281 }
282 
secondaryBuffer()283 IDirectSoundBuffer8 *OutputDirectSound::secondaryBuffer()
284 {
285     return m_dsBuffer;
286 }
287 
uninitialize()288 void OutputDirectSound::uninitialize()
289 {
290     m_dsBufferAt = 0;
291     if(m_dsBuffer)
292     {
293         m_dsBuffer->Stop();
294         m_dsBuffer->Release();
295         m_dsBuffer = nullptr;
296     }
297     if(m_primaryBuffer)
298     {
299         m_primaryBuffer->Stop();
300         m_primaryBuffer->Release();
301         m_primaryBuffer = nullptr;
302     }
303     if(m_ds)
304     {
305         m_ds->Release();
306         m_ds = nullptr;
307     }
308 }
309 
bytesToWrite()310 DWORD OutputDirectSound::bytesToWrite()
311 {
312     DWORD dsCurrentPlayCursor = 0;
313     m_dsBuffer->GetCurrentPosition((DWORD*)&dsCurrentPlayCursor, nullptr);
314     long available = dsCurrentPlayCursor - m_dsBufferAt; //available bytes
315 
316     if(available < 0)
317     {
318         available += DS_BUFSIZE;
319     }
320     return available;
321 }
322 
323 /***** MIXER *****/
VolumeDirectSound()324 VolumeDirectSound::VolumeDirectSound()
325 {
326     OutputDirectSound::volumeControl = this;
327     QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
328     m_volume.left = settings.value("DirectSound/left_volume", 100).toInt();
329     m_volume.right = settings.value("DirectSound/right_volume", 100).toInt();
330 }
331 
~VolumeDirectSound()332 VolumeDirectSound::~VolumeDirectSound()
333 {
334     m_volume = volume();
335     QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
336     settings.setValue("DirectSound/left_volume", m_volume.left);
337     settings.setValue("DirectSound/right_volume", m_volume.right);
338     OutputDirectSound::volumeControl = nullptr;
339 }
340 
setVolume(const VolumeSettings & vol)341 void VolumeDirectSound::setVolume(const VolumeSettings &vol)
342 {
343     if(OutputDirectSound::instance && OutputDirectSound::instance->secondaryBuffer())
344     {
345         int maxVol = qMax(vol.left, vol.right);
346         double voldB = -100.0, pandB = 0;
347         if(maxVol)
348         {
349             voldB = 20.0*log(maxVol/100.0)/log(10);
350             int balance = (vol.right - vol.left)*100.0/maxVol;
351             pandB = balance ? 20.0*log((100.0 - fabs(balance))/100.0)/log(10) : 0;
352             if(balance > 0)
353                 pandB = -pandB;
354         }
355         OutputDirectSound::instance->secondaryBuffer()->SetVolume(voldB*100);
356         OutputDirectSound::instance->secondaryBuffer()->SetPan(pandB*100);
357     }
358     m_volume = vol;
359 }
360 
volume() const361 VolumeSettings VolumeDirectSound::volume() const
362 {
363     VolumeSettings vol;
364     if(OutputDirectSound::instance && OutputDirectSound::instance->secondaryBuffer())
365     {
366         long v = 0;
367         double voldB = 0, pandB = 0;
368         OutputDirectSound::instance->secondaryBuffer()->GetVolume(&v);
369         voldB = v / 100.0;
370         OutputDirectSound::instance->secondaryBuffer()->GetPan(&v);
371         pandB = v / 100.0;
372         int volume = 100*pow(10, voldB/20.0);
373         int balance = 100 - 100*pow(10, abs(pandB)/20.0);
374         if(pandB > 0)
375             balance = -balance;
376         vol.left = volume-qMax(balance,0)*volume/100.0;
377         vol.right = volume+qMin(balance,0)*volume/100.0;
378         return vol;
379     }
380     return m_volume;
381 }
382 
restore()383 void VolumeDirectSound::restore()
384 {
385     setVolume(m_volume);
386 }
387