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