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