/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2016 Wang Bin * This file is part of QtAV (from 2015) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ******************************************************************************/ #include "AudioOutputBackend.h" #include "mkid.h" #include "factory.h" #include #include #include "AVCompat.h" #include "Logger.h" #define DX_LOG_COMPONENT "XAudio2" #include "DirectXHelper.h" #include "xaudio2_compat.h" // ref: DirectXTK, SDL, wine namespace FAV { static const char kName[] = "XAudio2"; #if(FE_LOG_VERSION) static int SOUND_BUFFER_LOG_COUNT = 0; #endif class AudioOutputXAudio2 Q_DECL_FINAL: public AudioOutputBackend, public IXAudio2VoiceCallback { public: AudioOutputXAudio2(QObject *parent = 0); ~AudioOutputXAudio2(); QString name() const Q_DECL_OVERRIDE { return QString::fromLatin1(kName);} QString status() const Q_DECL_OVERRIDE { return _status;}; // LOG bool open() Q_DECL_OVERRIDE; bool close() Q_DECL_OVERRIDE; // TODO: check channel layout. xaudio2 supports channels>2 bool isSupported(const AudioFormat& format) const Q_DECL_OVERRIDE; bool isSupported(AudioFormat::SampleFormat sampleFormat) const Q_DECL_OVERRIDE; bool isSupported(AudioFormat::ChannelLayout channelLayout) const Q_DECL_OVERRIDE; BufferControl bufferControl() const Q_DECL_OVERRIDE; void onCallback() Q_DECL_OVERRIDE; bool write(const QByteArray& data) Q_DECL_OVERRIDE; bool play() Q_DECL_OVERRIDE; bool setVolume(qreal value) Q_DECL_OVERRIDE; qreal getVolume() const Q_DECL_OVERRIDE; public: STDMETHOD_(void, OnVoiceProcessingPassStart)(THIS_ UINT32 bytesRequired) Q_DECL_OVERRIDE {Q_UNUSED(bytesRequired);} STDMETHOD_(void, OnVoiceProcessingPassEnd)(THIS) Q_DECL_OVERRIDE {} STDMETHOD_(void, OnStreamEnd)(THIS) Q_DECL_OVERRIDE {} STDMETHOD_(void, OnBufferStart)(THIS_ void* bufferContext) Q_DECL_OVERRIDE { Q_UNUSED(bufferContext);} STDMETHOD_(void, OnBufferEnd)(THIS_ void* bufferContext) Q_DECL_OVERRIDE { AudioOutputXAudio2 *ao = reinterpret_cast(bufferContext); if (ao->bufferControl() & AudioOutputBackend::CountCallback) { ao->onCallback(); } } STDMETHOD_(void, OnLoopEnd)(THIS_ void* bufferContext) Q_DECL_OVERRIDE { Q_UNUSED(bufferContext);} STDMETHOD_(void, OnVoiceError)(THIS_ void* bufferContext, HRESULT error) Q_DECL_OVERRIDE { Q_UNUSED(bufferContext); qWarning() << __FUNCTION__ << ": (" << error << ") " << qt_error_string(error); } private: QString _status; bool xaudio2_winsdk; bool uninit_com; // TODO: com ptr IXAudio2SourceVoice* source_voice; union { struct { DXSDK::IXAudio2* xaudio; DXSDK::IXAudio2MasteringVoice* master; } dxsdk; struct { WinSDK::IXAudio2* xaudio; WinSDK::IXAudio2MasteringVoice* master; } winsdk; }; QSemaphore sem; int queue_data_write; QByteArray queue_data; QLibrary dll; }; typedef AudioOutputXAudio2 AudioOutputBackendXAudio2; static const AudioOutputBackendId AudioOutputBackendId_XAudio2 = mkid::id32base36_6<'X', 'A', 'u', 'd', 'i', 'o'>::value; FACTORY_REGISTER(AudioOutputBackend, XAudio2, kName) AudioOutputXAudio2::AudioOutputXAudio2(QObject *parent) : AudioOutputBackend(AudioOutput::DeviceFeatures()|AudioOutput::SetVolume, parent) , xaudio2_winsdk(true) , uninit_com(false) , source_voice(NULL) , queue_data_write(0) { _status = ""; memset(&dxsdk, 0, sizeof(dxsdk)); available = false; //setDeviceFeatures(AudioOutput::DeviceFeatures()|AudioOutput::SetVolume); #ifdef Q_OS_WINRT qDebug("XAudio2 for WinRT"); // winrt can only load package dlls DX_ENSURE(XAudio2Create(&winsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)); #else // https://github.com/wang-bin/QtAV/issues/518 // already initialized in qtcore for main thread. If RPC_E_CHANGED_MODE no ref is added, CoUninitialize can lead to crash uninit_com = ::CoInitializeEx(NULL, COINIT_MULTITHREADED) != RPC_E_CHANGED_MODE; // load dll. = 7; ver--) { dll.setFileName(QStringLiteral("XAudio2_%1").arg(ver)); qDebug() << "AUDIO:" << dll.fileName(); if (!dll.load()) { qWarning() << dll.errorString(); continue; } _status += QString().sprintf("\n XAUDIO VER:2.%d\n",ver); #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) // defined as an inline function qDebug("Build with XAudio2 from DXSDK"); #else qDebug("Build with XAudio2 from Win8 or later SDK"); #endif bool ready = false; if (!ready && ver >= 8) { xaudio2_winsdk = true; _status += " XAUDIO MODE:WINSDK\n"; qDebug("Try symbol 'XAudio2Create' from WinSDK dll"); typedef HRESULT (__stdcall *XAudio2Create_t)(WinSDK::IXAudio2** ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor); XAudio2Create_t XAudio2Create = (XAudio2Create_t)dll.resolve("XAudio2Create"); if (XAudio2Create) { ready = SUCCEEDED(XAudio2Create(&winsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)); } } if (!ready && ver < 8) { xaudio2_winsdk = false; #ifdef _XBOX // xbox < win8 is inline XAudio2Create qDebug("Try symbol 'XAudio2Create' from DXSDK dll (XBOX)"); typedef HRESULT (__stdcall *XAudio2Create_t)(DXSDK::IXAudio2** ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor); XAudio2Create_t XAudio2Create = (XAudio2Create_t)dll.resolve("XAudio2Create"); if (XAudio2Create) ready = SUCCEEDED(XAudio2Create(&dxsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)); #else _status += " XAUDIO MODE:DXSDK\n"; // try xaudio2 from dxsdk without symbol qDebug("Try inline function 'XAudio2Create' from DXSDK"); ready = SUCCEEDED(DXSDK::XAudio2Create(&dxsdk.xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)); #endif } if (ready) { //qInfo() << "(991) XAUDIO:" << QString().sprintf("XAUDIO VER:2.%d SDK:%d CREATED:%d",ver,xaudio2_winsdk,ready); //qInfo() << "XAUDIO:" << dll.fileName(); break; } dll.unload(); } #endif //Q_OS_WINRT qDebug("xaudio2: %p", winsdk.xaudio); available = !!(winsdk.xaudio); // qInfo() << "(991) XAUDIO:" << QString().sprintf("XAUDIO STATUS:%d",available) << __FUNCTION__; _status += QString().sprintf(" XAUDIO STATUS:%d\n",available); } AudioOutputXAudio2::~AudioOutputXAudio2() { qDebug(); if (xaudio2_winsdk) SafeRelease(&winsdk.xaudio); else SafeRelease(&dxsdk.xaudio); #ifndef Q_OS_WINRT //again, for COM. not for winrt if (uninit_com) CoUninitialize(); #endif //Q_OS_WINRT } bool AudioOutputXAudio2::open() { if (!available) return false; #if (_WIN32_WINNT < _WIN32_WINNT_WIN8) // TODO: also check runtime version before call // XAUDIO2_DEVICE_DETAILS details; #endif WAVEFORMATEX wf; wf.cbSize = 0; //sdl: sizeof(wf) wf.nChannels = format.channels(); wf.nSamplesPerSec = format.sampleRate(); // FIXME: use supported values wf.wFormatTag = format.isFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM; wf.wBitsPerSample = format.bytesPerSample() * 8; wf.nBlockAlign = wf.nChannels * format.bytesPerSample(); wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; //qInfo() << "(991) XAUDIO " + QString().sprintf("SUPPORTED:%d CHANNELS:%d SAMPLES:%d",isSupported(format),wf.nChannels,wf.nSamplesPerSec); // dwChannelMask // TODO: channels >2, see dsound const UINT32 flags = 0; //XAUDIO2_VOICE_NOSRC | XAUDIO2_VOICE_NOPITCH; // TODO: sdl freq 1.0 if (xaudio2_winsdk) { // 보통 여기로 진행 // TODO: device Id property // TODO: parameters now default. // UINT32 Flags = 0, // LPCWSTR DeviceId = 0, DX_ENSURE_OK(winsdk.xaudio->CreateMasteringVoice(&winsdk.master, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE), false); DX_ENSURE_OK(winsdk.xaudio->CreateSourceVoice(&source_voice, &wf, flags, XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL), false); DX_ENSURE_OK(winsdk.xaudio->StartEngine(), false); //qInfo() << "XAUDIO OPENED OK." << __FUNCTION__ << __LINE__; //qInfo() << "WINSDK" << __FUNCTION__; } else { DX_ENSURE_OK(dxsdk.xaudio->CreateMasteringVoice(&dxsdk.master, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE), false); DX_ENSURE_OK(dxsdk.xaudio->CreateSourceVoice(&source_voice, &wf, flags, XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL), false); DX_ENSURE_OK(dxsdk.xaudio->StartEngine(), false); //qInfo() << "WINSDK ELSE" << __FUNCTION__; } // Start 무조건 0 DX_ENSURE_OK(source_voice->Start(0, XAUDIO2_COMMIT_NOW), false); qDebug("source_voice:%p", source_voice); queue_data.resize(buffer_size*buffer_count); //qInfo() << "(991) XAUDIO CREATE BUFFER " + QString().sprintf(" SIZE:%d COUNT:%d",buffer_size,buffer_count); sem.release(buffer_count - sem.available()); return true; } bool AudioOutputXAudio2::close() { //qInfo() << "(991) XAUDIO CLOSED"; qDebug("source_voice: %p, master: %p", source_voice, winsdk.master); if (source_voice) { source_voice->Stop(0, XAUDIO2_COMMIT_NOW); source_voice->FlushSourceBuffers(); source_voice->DestroyVoice(); source_voice = NULL; } if (xaudio2_winsdk) { if (winsdk.master) { winsdk.master->DestroyVoice(); winsdk.master = NULL; } if (winsdk.xaudio) winsdk.xaudio->StopEngine(); } else { if (dxsdk.master) { dxsdk.master->DestroyVoice(); dxsdk.master = NULL; } if (dxsdk.xaudio) dxsdk.xaudio->StopEngine(); } queue_data.clear(); queue_data_write = 0; //qInfo() << "XAUDIO CLOSED OK." << __FUNCTION__ << __LINE__; return true; } bool AudioOutputXAudio2::isSupported(const AudioFormat& format) const { return isSupported(format.sampleFormat()) && isSupported(format.channelLayout()); } bool AudioOutputXAudio2::isSupported(AudioFormat::SampleFormat sampleFormat) const { return !IsPlanar(sampleFormat) && RawSampleSize(sampleFormat) < sizeof(double); // TODO: what about s64? } // FIXME: bool AudioOutputXAudio2::isSupported(AudioFormat::ChannelLayout channelLayout) const { return channelLayout == AudioFormat::ChannelLayout_Mono || channelLayout == AudioFormat::ChannelLayout_Stereo; } AudioOutputBackend::BufferControl AudioOutputXAudio2::bufferControl() const { return CountCallback; } void AudioOutputXAudio2::onCallback() { if (bufferControl() & CountCallback) sem.release(); } bool AudioOutputXAudio2::write(const QByteArray &data) { #if(FE_LOG_VERSION) //if(SOUND_BUFFER_LOG_COUNT % 50 == 0) { //qInfo() << "(991) XAUDIO WRITE START"; //} #endif //qDebug("sem: %d, write: %d/%d", sem.available(), queue_data_write, queue_data.size()); if (bufferControl() & CountCallback) sem.acquire(); const int s = qMin(queue_data.size() - queue_data_write, data.size()); // assume data.size() <= buffer_size. It's true in QtAV if (s < data.size()) queue_data_write = 0; memcpy((char*)queue_data.constData() + queue_data_write, data.constData(), data.size()); XAUDIO2_BUFFER xb; //IMPORTANT! wrong value(playbegin/length, loopbegin/length) will result in commit sourcebuffer fail memset(&xb, 0, sizeof(XAUDIO2_BUFFER)); xb.AudioBytes = data.size(); //xb.Flags = XAUDIO2_END_OF_STREAM; xb.pContext = this; xb.pAudioData = (const BYTE*)(queue_data.constData() + queue_data_write); queue_data_write += data.size(); if (queue_data_write == queue_data.size()) queue_data_write = 0; DX_ENSURE_OK(source_voice->SubmitSourceBuffer(&xb, NULL), false); // TODO: XAUDIO2_E_DEVICE_INVALIDATED #if(FE_LOG_VERSION) //if(SOUND_BUFFER_LOG_COUNT % 50 == 0) { // qInfo() << "(991) XAUDIO WRITE" << QString().sprintf("%d",queue_data_write); //} SOUND_BUFFER_LOG_COUNT += 1; #endif return true; } bool AudioOutputXAudio2::play() { return true; } bool AudioOutputXAudio2::setVolume(qreal value) { // master or source? DX_ENSURE_OK(source_voice->SetVolume(value), false); return true; } qreal AudioOutputXAudio2::getVolume() const { FLOAT value; source_voice->GetVolume(&value); return value; } } // namespace FAV