aboutsummaryrefslogblamecommitdiff
path: root/src/plugins/Output/wasapi/outputwasapi.cpp
blob: 3b985429acd3170383b5d9b221504b38ce513b84 (plain) (tree)
1
2
                                                                             
                                                                             






























                                                                              

                                                                   
 
                                       
 



                                                      
                                          

                                                                  













                                                    



                         
                              











                                                                                                                                    




















                                                                                                                   


































                                                                                            
                                                  
      
                                              
          

                                               






                                               








                                                                                                                                         




                                                                                               
                                                                          




                                                                                                  
                                                                                                        
      
                                                                                               


                      
                                                                                                             
      
                                                                                               


                      
 
                                                   
      
                                                                                          


                      
                                      


                                             


                 






                                                                 


                       
                      

                                                
                                                      
 
                                                                             
 

                                    
      



                                                                          
      
 
                                                                             
      
                                                                                              

                   


                                                          



                           


                                                                 



                             
                            



                            
                            



                           


                             

  
                                                      
  

                                 


                                  
                       
      


                                   
      
                      
      

                                  
      
                  
      








                                    
      

                                         
      


                    
                             


                                                                  

                                                                         

  
                              


                                                                  

                                                              


                                     
                                                        
  
                                                                              
      
                                                                                                                    



                    
                                            

                        
                                                                              
      



                                                                              




                     
                             

                         
  
/***************************************************************************
 *   Copyright (C) 2016 by Ilya Kotov                                      *
 *   forkotov02@hotmail.ru                                                 *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include <QObject>
#include <QSettings>
#include <string.h>
#include <iostream>
#include <unistd.h>
#include <qmmp/buffer.h>
#include <math.h>
#include "outputwasapi.h"

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
const IID IID_IChannelAudioVolume = __uuidof(IChannelAudioVolume);
const IID IID_ISimpleAudioVolume = __uuidof(ISimpleAudioVolume);

#define WASAPI_BUFSIZE 20000000LL //2s

#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif

OutputWASAPI *OutputWASAPI::instance = 0;
VolumeWASAPI *OutputWASAPI::volumeControl = 0;
OutputWASAPI::DWASAPIChannels OutputWASAPI::m_wasapi_pos[10]  = {
   {Qmmp::CHAN_FRONT_LEFT, SPEAKER_FRONT_LEFT},
   {Qmmp::CHAN_FRONT_RIGHT, SPEAKER_FRONT_RIGHT},
   {Qmmp::CHAN_FRONT_CENTER, SPEAKER_FRONT_CENTER},
   {Qmmp::CHAN_LFE, SPEAKER_LOW_FREQUENCY},
   {Qmmp::CHAN_REAR_LEFT, SPEAKER_BACK_LEFT},
   {Qmmp::CHAN_REAR_RIGHT, SPEAKER_BACK_RIGHT},
   {Qmmp::CHAN_REAR_CENTER, SPEAKER_BACK_CENTER},
   {Qmmp::CHAN_SIDE_LEFT, SPEAKER_SIDE_LEFT},
   {Qmmp::CHAN_SIDE_RIGHT, SPEAKER_BACK_RIGHT},
   {Qmmp::CHAN_NULL, 0}
};

OutputWASAPI::OutputWASAPI() : Output()
{
    m_pEnumerator = 0;
    m_pDevice = 0;
    m_pAudioClient = 0;
    m_pRenderClient = 0;
    m_pSimpleAudioVolume = 0;
    instance = this;
}

OutputWASAPI::~OutputWASAPI()
{
    instance = 0;
    uninitialize();
}

bool OutputWASAPI::initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format)
{
    HRESULT result = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&m_pEnumerator);
    if(result != S_OK)
    {
        qWarning("OutputWASAPI: CoCreateInstance failed, error code = 0x%lx", result);
        m_pEnumerator = 0;
        return false;
    }

    if((result = m_pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_pDevice)) != S_OK)
    {
        qWarning("OutputWASAPI: IMMDeviceEnumerator::GetDefaultAudioEndpoint failed, error code = 0x%lx", result);
        m_pDevice = 0;
        return false;
    }

    if((result = m_pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&m_pAudioClient)) != S_OK)
    {
        qWarning("OutputWASAPI: IMMDevice::Activate failed, error code = 0x%lx", result);
        m_pAudioClient = 0;
        return false;
    }

    WAVEFORMATEXTENSIBLE wfex;
    wfex.Format.wFormatTag      = WAVE_FORMAT_EXTENSIBLE;
    wfex.Format.nChannels       = map.count();
    wfex.Format.nSamplesPerSec  = freq;
    wfex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE);

    if(format == Qmmp::PCM_S16LE)
    {
        wfex.Format.wBitsPerSample = 16;
        wfex.Samples.wValidBitsPerSample = 16;
    }
    else if(format == Qmmp::PCM_S24LE)
    {
        wfex.Format.wBitsPerSample  = 32;
        wfex.Samples.wValidBitsPerSample = 24;
    }
    else if(format == Qmmp::PCM_S32LE)
    {
        wfex.Format.wBitsPerSample  = 32;
        wfex.Samples.wValidBitsPerSample = 32;
    }
    else
    {
        format = Qmmp::PCM_S16LE;
        wfex.Format.wBitsPerSample  = 16;
        wfex.Samples.wValidBitsPerSample = 16;
    }

    wfex.Format.nBlockAlign     = (wfex.Format.wBitsPerSample / 8) * wfex.Format.nChannels;
    wfex.Format.nAvgBytesPerSec = wfex.Format.nSamplesPerSec * wfex.Format.nBlockAlign;

    //generate channel order
    ChannelMap out_map;
    int i = 0;
    DWORD mask = 0;
    while(m_wasapi_pos[i].pos != Qmmp::CHAN_NULL)
    {
        if(map.contains(m_wasapi_pos[i].pos))
        {
            mask |= m_wasapi_pos[i].chan_mask;
            out_map << m_wasapi_pos[i].pos;
        }
        i++;
    }

    wfex.dwChannelMask = mask;
    wfex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;

    DWORD streamFlags = 0;
    //enable channel matrixer and a sample rate converter if format is unsupported
    if(m_pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX *)&wfex, 0) != S_OK)
    {
        streamFlags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
        qDebug("OutputWASAPI: format is not supported, using converter");
    }

    if((result = m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, streamFlags, 20000000LL, 0, (WAVEFORMATEX *)&wfex, NULL)) != S_OK)
    {
        qWarning("OutputWASAPI: IAudioClient::Initialize failed, error code = 0x%lx", result);
        return false;
    }

    if((result = m_pAudioClient->GetBufferSize(&m_bufferFrames)) != S_OK)
    {
        qWarning("OutputWASAPI: IAudioClient::GetBufferSize failed, error code = 0x%lx", result);
        return false;
    }

    if((result = m_pAudioClient->GetService(IID_IAudioRenderClient, (void**)&m_pRenderClient)) != S_OK)
    {
        qWarning("OutputWASAPI: IAudioClient::GetService failed, error code = 0x%lx", result);
        return false;
    }

    if((result = m_pAudioClient->GetService(IID_ISimpleAudioVolume, (void**)&m_pSimpleAudioVolume)) != S_OK)
    {
        qWarning("OutputWASAPI: IAudioClient::GetService failed, error code = 0x%lx", result);
        return false;
    }


    if((result = m_pAudioClient->Start()) != S_OK)
    {
        qWarning("OutputWASAPI: IAudioClient::Start failed, error code = 0x%lx", result);
        return false;
    }

    configure(freq, out_map, format);
    m_frameSize = sampleSize() * channels();
    if(volumeControl)
        volumeControl->restore();
    return true;
}

qint64 OutputWASAPI::latency()
{
    return 0;
}

qint64 OutputWASAPI::writeAudio(unsigned char *data, qint64 len)
{
    UINT32 frames = 0;
    BYTE *pData = 0;
    DWORD flags = 0;
    DWORD result = 0;
    m_pAudioClient->GetCurrentPadding(&frames);

    UINT32 framesAvailable = m_bufferFrames - frames;

    UINT32 framesToWrite = qMin(framesAvailable, (UINT32)len / m_frameSize);

    //wait until buffer is not full
    if(framesToWrite == 0)
    {
        usleep(m_bufferFrames * 1000000L / sampleRate() / 2);
        m_pAudioClient->GetCurrentPadding(&frames);
        framesAvailable = m_bufferFrames - frames;
        framesToWrite = qMin(framesAvailable, (UINT32)len / m_frameSize);
    }

    if((result = m_pRenderClient->GetBuffer(framesToWrite, &pData)) != S_OK)
    {
        qWarning("OutputWASAPI: IAudioClient::GetBuffer failed, error code = 0x%lx", result);
        return -1;
    }
    memcpy(pData, data, framesToWrite * m_frameSize);
    m_pRenderClient->ReleaseBuffer(framesToWrite, flags);
    return framesToWrite * m_frameSize;
}

void OutputWASAPI::drain()
{
    UINT32 frames = 0;
    m_pAudioClient->GetCurrentPadding(&frames);
    usleep((m_bufferFrames - frames) * 1000000L / sampleRate());
}

void OutputWASAPI::suspend()
{
    m_pAudioClient->Stop();
}

void OutputWASAPI::resume()
{
   m_pAudioClient->Start();
}

void OutputWASAPI::reset()
{
    m_pAudioClient->Stop();
    m_pAudioClient->Reset();
    m_pAudioClient->Start();
}

ISimpleAudioVolume *OutputWASAPI::simpleAudioVolume()
{
    return m_pSimpleAudioVolume;
}

void OutputWASAPI::uninitialize()
{
    if(m_pAudioClient)
    {
        m_pAudioClient->Stop();
        m_pAudioClient->Release();
        m_pAudioClient = 0;
    }
    if(m_pEnumerator)
    {
        m_pEnumerator->Release();
        m_pEnumerator = 0;
    }
    if(m_pDevice)
    {
        m_pDevice->Release();
        m_pDevice = 0;
    }
    if(m_pRenderClient)
    {
        m_pRenderClient->Release();
        m_pRenderClient = 0;
    }
    if(m_pSimpleAudioVolume)
    {
        m_pSimpleAudioVolume->Release();
        m_pSimpleAudioVolume = 0;
    }
}

/***** MIXER *****/
VolumeWASAPI::VolumeWASAPI()
{
    OutputWASAPI::volumeControl = this;
    QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
    m_volume.left = settings.value("WASAPI/left_volume", 100).toInt();
    m_volume.right = settings.value("WASAPI/right_volume", 100).toInt();
}

VolumeWASAPI::~VolumeWASAPI()
{
    m_volume = volume();
    QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
    settings.setValue("WASAPI/left_volume", m_volume.left);
    settings.setValue("WASAPI/right_volume", m_volume.right);
    OutputWASAPI::volumeControl = 0;
}

void VolumeWASAPI::setVolume(const VolumeSettings &vol)
{
    if(OutputWASAPI::instance && OutputWASAPI::instance->simpleAudioVolume())
    {
        OutputWASAPI::instance->simpleAudioVolume()->SetMasterVolume(float(qMax(vol.left, vol.right)) / 100.0f, 0);
    }
    m_volume = vol;
}

VolumeSettings VolumeWASAPI::volume() const
{
    VolumeSettings vol;
    if(OutputWASAPI::instance && OutputWASAPI::instance->simpleAudioVolume())
    {
        float level = 0;
        OutputWASAPI::instance->simpleAudioVolume()->GetMasterVolume(&level);
        vol.left = ceilf(level * 100.0f);
        vol.right = vol.left;
        return vol;
    }
    return m_volume;
}

void VolumeWASAPI::restore()
{
    setVolume(m_volume);
}