1 /******************************************************************************
2 QtAV: Multimedia framework based on Qt and FFmpeg
3 Copyright (C) 2012-2016 Wang Bin <wbsecg1@gmail.com>
4
5 * This file is part of QtAV (from 2015)
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 ******************************************************************************/
21
22 #include "QtAV/private/AudioOutputBackend.h"
23 #include "QtAV/private/mkid.h"
24 #include "QtAV/private/factory.h"
25 #include <QtCore/QLibrary>
26 #include <QtCore/QSemaphore>
27 #include "QtAV/private/AVCompat.h"
28 #include "utils/Logger.h"
29 #define DX_LOG_COMPONENT "XAudio2"
30 #include "utils/DirectXHelper.h"
31 #include "xaudio2_compat.h"
32
33 // ref: DirectXTK, SDL, wine
34 namespace QtAV {
35
36 static const char kName[] = "XAudio2";
37 class AudioOutputXAudio2 Q_DECL_FINAL: public AudioOutputBackend, public IXAudio2VoiceCallback
38 {
39 public:
40 AudioOutputXAudio2(QObject *parent = 0);
41 ~AudioOutputXAudio2();
name() const42 QString name() const Q_DECL_OVERRIDE { return QString::fromLatin1(kName);}
43 bool open() Q_DECL_OVERRIDE;
44 bool close() Q_DECL_OVERRIDE;
45 // TODO: check channel layout. xaudio2 supports channels>2
46 bool isSupported(const AudioFormat& format) const Q_DECL_OVERRIDE;
47 bool isSupported(AudioFormat::SampleFormat sampleFormat) const Q_DECL_OVERRIDE;
48 bool isSupported(AudioFormat::ChannelLayout channelLayout) const Q_DECL_OVERRIDE;
49 BufferControl bufferControl() const Q_DECL_OVERRIDE;
50 void onCallback() Q_DECL_OVERRIDE;
51 bool write(const QByteArray& data) Q_DECL_OVERRIDE;
52 bool play() Q_DECL_OVERRIDE;
53
54 bool setVolume(qreal value) Q_DECL_OVERRIDE;
55 qreal getVolume() const Q_DECL_OVERRIDE;
56 public:
STDMETHOD_(void,OnVoiceProcessingPassStart)57 STDMETHOD_(void, OnVoiceProcessingPassStart)(THIS_ UINT32 bytesRequired) Q_DECL_OVERRIDE {Q_UNUSED(bytesRequired);}
STDMETHOD_(void,OnVoiceProcessingPassEnd)58 STDMETHOD_(void, OnVoiceProcessingPassEnd)(THIS) Q_DECL_OVERRIDE {}
STDMETHOD_(void,OnStreamEnd)59 STDMETHOD_(void, OnStreamEnd)(THIS) Q_DECL_OVERRIDE {}
STDMETHOD_(void,OnBufferStart)60 STDMETHOD_(void, OnBufferStart)(THIS_ void* bufferContext) Q_DECL_OVERRIDE { Q_UNUSED(bufferContext);}
STDMETHOD_(void,OnBufferEnd)61 STDMETHOD_(void, OnBufferEnd)(THIS_ void* bufferContext) Q_DECL_OVERRIDE {
62 AudioOutputXAudio2 *ao = reinterpret_cast<AudioOutputXAudio2*>(bufferContext);
63 if (ao->bufferControl() & AudioOutputBackend::CountCallback) {
64 ao->onCallback();
65 }
66 }
STDMETHOD_(void,OnLoopEnd)67 STDMETHOD_(void, OnLoopEnd)(THIS_ void* bufferContext) Q_DECL_OVERRIDE { Q_UNUSED(bufferContext);}
STDMETHOD_(void,OnVoiceError)68 STDMETHOD_(void, OnVoiceError)(THIS_ void* bufferContext, HRESULT error) Q_DECL_OVERRIDE {
69 Q_UNUSED(bufferContext);
70 qWarning() << __FUNCTION__ << ": (" << error << ") " << qt_error_string(error);
71 }
72
73 private:
74 bool xaudio2_winsdk;
75 bool uninit_com;
76 // TODO: com ptr
77 IXAudio2SourceVoice* source_voice;
78 union {
79 struct {
80 DXSDK::IXAudio2* xaudio;
81 DXSDK::IXAudio2MasteringVoice* master;
82 } dxsdk;
83 struct {
84 WinSDK::IXAudio2* xaudio;
85 WinSDK::IXAudio2MasteringVoice* master;
86 } winsdk;
87 };
88 QSemaphore sem;
89 int queue_data_write;
90 QByteArray queue_data;
91
92 QLibrary dll;
93 };
94
95 typedef AudioOutputXAudio2 AudioOutputBackendXAudio2;
96 static const AudioOutputBackendId AudioOutputBackendId_XAudio2 = mkid::id32base36_6<'X', 'A', 'u', 'd', 'i', 'o'>::value;
FACTORY_REGISTER(AudioOutputBackend,XAudio2,kName)97 FACTORY_REGISTER(AudioOutputBackend, XAudio2, kName)
98
99 AudioOutputXAudio2::AudioOutputXAudio2(QObject *parent)
100 : AudioOutputBackend(AudioOutput::DeviceFeatures()|AudioOutput::SetVolume, parent)
101 , xaudio2_winsdk(true)
102 , uninit_com(false)
103 , source_voice(NULL)
104 , queue_data_write(0)
105 {
106 memset(&dxsdk, 0, sizeof(dxsdk));
107 available = false;
108 //setDeviceFeatures(AudioOutput::DeviceFeatures()|AudioOutput::SetVolume);
109 #ifdef Q_OS_WINRT
110 qDebug("XAudio2 for WinRT");
111 // winrt can only load package dlls
112 DX_ENSURE(XAudio2Create(&winsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR));
113 #else
114 // https://github.com/wang-bin/QtAV/issues/518
115 // already initialized in qtcore for main thread. If RPC_E_CHANGED_MODE no ref is added, CoUninitialize can lead to crash
116 uninit_com = CoInitializeEx(NULL, COINIT_MULTITHREADED) != RPC_E_CHANGED_MODE;
117 // load dll. <win8: XAudio2_7.DLL, <win10: XAudio2_8.DLL, win10: XAudio2_9.DLL. also defined by XAUDIO2_DLL_A in xaudio2.h
118 int ver = 9;
119 for (; ver >= 7; ver--) {
120 dll.setFileName(QStringLiteral("XAudio2_%1").arg(ver));
121 qDebug() << dll.fileName();
122 if (!dll.load()) {
123 qWarning() << dll.errorString();
124 continue;
125 }
126 #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
127 // defined as an inline function
128 qDebug("Build with XAudio2 from DXSDK");
129 #else
130 qDebug("Build with XAudio2 from Win8 or later SDK");
131 #endif
132 bool ready = false;
133 if (!ready && ver >= 8) {
134 xaudio2_winsdk = true;
135 qDebug("Try symbol 'XAudio2Create' from WinSDK dll");
136 typedef HRESULT (__stdcall *XAudio2Create_t)(WinSDK::IXAudio2** ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
137 XAudio2Create_t XAudio2Create = (XAudio2Create_t)dll.resolve("XAudio2Create");
138 if (XAudio2Create)
139 ready = SUCCEEDED(XAudio2Create(&winsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR));
140 }
141 if (!ready && ver < 8) {
142 xaudio2_winsdk = false;
143 #ifdef _XBOX // xbox < win8 is inline XAudio2Create
144 qDebug("Try symbol 'XAudio2Create' from DXSDK dll (XBOX)");
145 typedef HRESULT (__stdcall *XAudio2Create_t)(DXSDK::IXAudio2** ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
146 XAudio2Create_t XAudio2Create = (XAudio2Create_t)dll.resolve("XAudio2Create");
147 if (XAudio2Create)
148 ready = SUCCEEDED(XAudio2Create(&dxsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR));
149 #else
150 // try xaudio2 from dxsdk without symbol
151 qDebug("Try inline function 'XAudio2Create' from DXSDK");
152 ready = SUCCEEDED(DXSDK::XAudio2Create(&dxsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR));
153 #endif
154 }
155 if (ready)
156 break;
157 dll.unload();
158 }
159 #endif //Q_OS_WINRT
160 qDebug("xaudio2: %p", winsdk.xaudio);
161 available = !!(winsdk.xaudio);
162 }
163
~AudioOutputXAudio2()164 AudioOutputXAudio2::~AudioOutputXAudio2()
165 {
166 qDebug();
167 if (xaudio2_winsdk)
168 SafeRelease(&winsdk.xaudio);
169 else
170 SafeRelease(&dxsdk.xaudio);
171 #ifndef Q_OS_WINRT
172 //again, for COM. not for winrt
173 if (uninit_com)
174 CoUninitialize();
175 #endif //Q_OS_WINRT
176 }
177
open()178 bool AudioOutputXAudio2::open()
179 {
180 if (!available)
181 return false;
182 #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) // TODO: also check runtime version before call
183 // XAUDIO2_DEVICE_DETAILS details;
184 #endif
185
186 WAVEFORMATEX wf;
187 wf.cbSize = 0; //sdl: sizeof(wf)
188 wf.nChannels = format.channels();
189 wf.nSamplesPerSec = format.sampleRate(); // FIXME: use supported values
190 wf.wFormatTag = format.isFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
191 wf.wBitsPerSample = format.bytesPerSample() * 8;
192 wf.nBlockAlign = wf.nChannels * format.bytesPerSample();
193 wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
194 // dwChannelMask
195 // TODO: channels >2, see dsound
196 const UINT32 flags = 0; //XAUDIO2_VOICE_NOSRC | XAUDIO2_VOICE_NOPITCH;
197 // TODO: sdl freq 1.0
198 if (xaudio2_winsdk) {
199 // TODO: device Id property
200 // TODO: parameters now default.
201 DX_ENSURE_OK(winsdk.xaudio->CreateMasteringVoice(&winsdk.master, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE), false);
202 DX_ENSURE_OK(winsdk.xaudio->CreateSourceVoice(&source_voice, &wf, flags, XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL), false);
203 DX_ENSURE_OK(winsdk.xaudio->StartEngine(), false);
204 } else {
205 DX_ENSURE_OK(dxsdk.xaudio->CreateMasteringVoice(&dxsdk.master, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE), false);
206 DX_ENSURE_OK(dxsdk.xaudio->CreateSourceVoice(&source_voice, &wf, flags, XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL), false);
207 DX_ENSURE_OK(dxsdk.xaudio->StartEngine(), false);
208 }
209 DX_ENSURE_OK(source_voice->Start(0, XAUDIO2_COMMIT_NOW), false);
210 qDebug("source_voice:%p", source_voice);
211
212 queue_data.resize(buffer_size*buffer_count);
213 sem.release(buffer_count - sem.available());
214 return true;
215 }
216
close()217 bool AudioOutputXAudio2::close()
218 {
219 qDebug("source_voice: %p, master: %p", source_voice, winsdk.master);
220 if (source_voice) {
221 source_voice->Stop(0, XAUDIO2_COMMIT_NOW);
222 source_voice->FlushSourceBuffers();
223 source_voice->DestroyVoice();
224 source_voice = NULL;
225 }
226 if (xaudio2_winsdk) {
227 if (winsdk.master) {
228 winsdk.master->DestroyVoice();
229 winsdk.master = NULL;
230 }
231 if (winsdk.xaudio)
232 winsdk.xaudio->StopEngine();
233 } else {
234 if (dxsdk.master) {
235 dxsdk.master->DestroyVoice();
236 dxsdk.master = NULL;
237 }
238 if (dxsdk.xaudio)
239 dxsdk.xaudio->StopEngine();
240 }
241
242 queue_data.clear();
243 queue_data_write = 0;
244 return true;
245 }
246
isSupported(const AudioFormat & format) const247 bool AudioOutputXAudio2::isSupported(const AudioFormat& format) const
248 {
249 return isSupported(format.sampleFormat()) && isSupported(format.channelLayout());
250 }
251
isSupported(AudioFormat::SampleFormat sampleFormat) const252 bool AudioOutputXAudio2::isSupported(AudioFormat::SampleFormat sampleFormat) const
253 {
254 return !IsPlanar(sampleFormat) && RawSampleSize(sampleFormat) < sizeof(double); // TODO: what about s64?
255 }
256
257 // FIXME:
isSupported(AudioFormat::ChannelLayout channelLayout) const258 bool AudioOutputXAudio2::isSupported(AudioFormat::ChannelLayout channelLayout) const
259 {
260 return channelLayout == AudioFormat::ChannelLayout_Mono || channelLayout == AudioFormat::ChannelLayout_Stereo;
261 }
262
bufferControl() const263 AudioOutputBackend::BufferControl AudioOutputXAudio2::bufferControl() const
264 {
265 return CountCallback;
266 }
267
onCallback()268 void AudioOutputXAudio2::onCallback()
269 {
270 if (bufferControl() & CountCallback)
271 sem.release();
272 }
273
write(const QByteArray & data)274 bool AudioOutputXAudio2::write(const QByteArray &data)
275 {
276 //qDebug("sem: %d, write: %d/%d", sem.available(), queue_data_write, queue_data.size());
277 if (bufferControl() & CountCallback)
278 sem.acquire();
279 const int s = qMin(queue_data.size() - queue_data_write, data.size());
280 // assume data.size() <= buffer_size. It's true in QtAV
281 if (s < data.size())
282 queue_data_write = 0;
283 memcpy((char*)queue_data.constData() + queue_data_write, data.constData(), data.size());
284 XAUDIO2_BUFFER xb; //IMPORTANT! wrong value(playbegin/length, loopbegin/length) will result in commit sourcebuffer fail
285 memset(&xb, 0, sizeof(XAUDIO2_BUFFER));
286 xb.AudioBytes = data.size();
287 //xb.Flags = XAUDIO2_END_OF_STREAM;
288 xb.pContext = this;
289 xb.pAudioData = (const BYTE*)(queue_data.constData() + queue_data_write);
290 queue_data_write += data.size();
291 if (queue_data_write == queue_data.size())
292 queue_data_write = 0;
293 DX_ENSURE_OK(source_voice->SubmitSourceBuffer(&xb, NULL), false);
294 // TODO: XAUDIO2_E_DEVICE_INVALIDATED
295 return true;
296 }
297
play()298 bool AudioOutputXAudio2::play()
299 {
300 return true;
301 }
302
setVolume(qreal value)303 bool AudioOutputXAudio2::setVolume(qreal value)
304 {
305 // master or source?
306 DX_ENSURE_OK(source_voice->SetVolume(value), false);
307 return true;
308 }
309
getVolume() const310 qreal AudioOutputXAudio2::getVolume() const
311 {
312 FLOAT value;
313 source_voice->GetVolume(&value);
314 return value;
315 }
316
317 } // namespace QtAV
318