2

I have a program using Qt and I need to make it play sounds using an output device that user selects in preferences. I can list all available devices that are in windows by calling this code:

QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
foreach (QAudioDeviceInfo i, devices)
    this->ui->comboBox->addItem(i.deviceName());

However I have no idea how can I change the device that would be a default device for my application so that QMediaPlayer would play all sounds using that device, instead of a default one. How can I do that? I am fine with windows only Qt5+ specific solution, although cross-platform solution would be probably best.

Basically I want to implement similar preferences dialog like this one from Microsoft Lync:

enter image description here

According to microsoft: https://social.technet.microsoft.com/Forums/windows/en-US/b1d1acac-1f21-4d23-8d68-98964d67c2c7/assigning-an-application-to-different-sound-outputs windows 7 introduced API's that can do that. However I have no idea how and where is that documented.

Petr
  • 13,747
  • 20
  • 89
  • 144
  • mb this will help you http://doc.qt.io/qt-5/qaudiooutput.html – Mike Minaev Feb 09 '15 at 11:44
  • @MikeMinaev I have already read that, it doesn't help as it states no information about how can programmer change the default device. I can only find how to do this in Qt4 with phonon, but there is no information for Qt5 – Petr Feb 09 '15 at 11:53
  • According to http://stackoverflow.com/questions/2175318/how-to-change-default-sound-playback-device-programatically i think that you can't change it, i think that it is system setting that you can't change, but there is some magic to do it. – Mike Minaev Feb 09 '15 at 11:56

3 Answers3

1

It seems you are supposed to do something like this (where player is QMediaPlayer*):

QMediaService *svc = player->service();
if (svc != nullptr)
{
    QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
                                       (svc->requestControl(QAudioOutputSelectorControl_iid));
    if (out != nullptr)
    {
        out->setActiveOutput(this->ui->comboBox->currentText());
        svc->releaseControl(out);
    }
}

But due to this and this I am unable to test this on my Win7 installation.

UPDATE:

Well, here's the fix I made (tested with Desktop_Qt_5_4_0_MSVC2013_32/64-Debug/Release; you might need to change the vtable code for different toolchains):

How to get the list of devices with their 'friendly names' (key - name; value - deviceID):

auto outputs = MFAudioEndpointControl_Fixed::availableOutputsFriendly();
for (auto it = outputs.cbegin(), e = outputs.cend(); it != e; ++it)
{
    this->ui->comboBox->addItem(it.key(), it.value());
    this->ui->plainTextEdit->appendPlainText(it.key() + " (" + it.value() + ")");
}

You will have to apply the following fix for each QMediaPlayer you create:

QMediaPlayer *player = new QMediaPlayer(this);

QMediaService *svc = player->service();
if (svc != nullptr)
{
    QAudioOutputSelectorControl *out = reinterpret_cast<QAudioOutputSelectorControl*>
                                       (svc->requestControl(QAudioOutputSelectorControl_iid));
    if (out != nullptr)
    {
        new MFAudioEndpointControl_Fixed_Helper(out); // <- the fix; notice that it's a HELPER class

        out->setActiveOutput(this->ui->comboBox->itemData(this->ui->comboBox->currentIndex()).toString()); // we have to pass deviceID, not the name
        svc->releaseControl(out);
    }
}
Desu_Never_Lies
  • 690
  • 3
  • 10
  • QAudioOutputSelectorControl *control = qobject_cast(svc->requestControl(QAudioOutputSelectorControl_iid)) This always return nullptr in Qt5.12.8 on Linux – Mike Jul 12 '23 at 05:40
1

I have tried the solution above with less success. So I put mine here. It works fine on win10 too (same method - undocumented API):

  1. First add to your .pro file:

    QT += axcontainer
    
  2. Then add the following modified to fit QT header (name it: PolicyConfig.h)

    // ----------------------------------------------------------------------------
    // PolicyConfig.h
    // Undocumented COM-interface IPolicyConfig.
    // Use for set default audio render endpoint
    // @author EreTIk
    // ----------------------------------------------------------------------------
    
    
    #pragma once
    
    #include "winnt.h"
    //#include <QAxAggregated>
    
    class DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
    class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
    // ----------------------------------------------------------------------------
    // class CPolicyConfigClient
    // {870af99c-171d-4f9e-af0d-e63df40c2bc9}
    //  
    // interface IPolicyConfig
    // {f8679f50-850a-41cf-9c72-430f290290c8}
    //
    // Query interface:
    // CComPtr<IPolicyConfig> PolicyConfig;
    // PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient));
    // 
    // @compatible: Windows 7 and Later
    // ----------------------------------------------------------------------------
    class IPolicyConfig : public IUnknown
    {
    public:
    
        virtual HRESULT GetMixFormat(
            PCWSTR,
            WAVEFORMATEX **
        );
    
        virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
            PCWSTR,
            INT,
            WAVEFORMATEX **
        );
    
        virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
            PCWSTR
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
            PCWSTR,
            WAVEFORMATEX *,
            WAVEFORMATEX *
        );
    
        virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
            PCWSTR,
            INT,
            PINT64,
            PINT64
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
            PCWSTR,
            PINT64
        );
    
        virtual HRESULT STDMETHODCALLTYPE GetShareMode(
            PCWSTR,
            struct DeviceShareMode *
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetShareMode(
            PCWSTR,
            struct DeviceShareMode *
        );
    
        virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
            PCWSTR,
            const PROPERTYKEY &,
            PROPVARIANT *
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
            PCWSTR,
            const PROPERTYKEY &,
            PROPVARIANT *
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
            __in PCWSTR wszDeviceId,
            __in ERole eRole 
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
            PCWSTR,
            INT
        );
    };
    
    class DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista;
    class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient;
    // ----------------------------------------------------------------------------
    // class CPolicyConfigVistaClient
    // {294935CE-F637-4E7C-A41B-AB255460B862}
    //  
    // interface IPolicyConfigVista
    // {568b9108-44bf-40b4-9006-86afe5b5a620}
    //
    // Query interface:
    // CComPtr<IPolicyConfigVista> PolicyConfig;
    // PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient));
    // 
    // @compatible: Windows Vista and Later
    // ----------------------------------------------------------------------------
    class IPolicyConfigVista : public IUnknown
    {
    public:
    
        virtual HRESULT GetMixFormat(
            PCWSTR,
            WAVEFORMATEX **
        );  // not available on Windows 7, use method from IPolicyConfig
    
        virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
            PCWSTR,
            INT,
            WAVEFORMATEX **
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
            PCWSTR,
            WAVEFORMATEX *,
            WAVEFORMATEX *
        );
    
        virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
            PCWSTR,
            INT,
            PINT64,
            PINT64
        );  // not available on Windows 7, use method from IPolicyConfig
    
        virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
            PCWSTR,
            PINT64
        );  // not available on Windows 7, use method from IPolicyConfig
    
        virtual HRESULT STDMETHODCALLTYPE GetShareMode(
            PCWSTR,
            struct DeviceShareMode *
        );  // not available on Windows 7, use method from IPolicyConfig
    
        virtual HRESULT STDMETHODCALLTYPE SetShareMode(
            PCWSTR,
            struct DeviceShareMode *
        );  // not available on Windows 7, use method from IPolicyConfig
    
        virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
            PCWSTR,
            const PROPERTYKEY &,
            PROPVARIANT *
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
            PCWSTR,
            const PROPERTYKEY &,
            PROPVARIANT *
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
            __in PCWSTR wszDeviceId,
            __in ERole eRole 
        );
    
        virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
            PCWSTR,
            INT
        );  // not available on Windows 7, use method from IPolicyConfig
    };
    
  3. Add the Qt interface DefaultOutput

    #ifndef DEFAULTOUTPUT_H
    #define DEFAULTOUTPUT_H
    
    #include <QString>
    #include <QHash>
    
    class DefaultOutput
    {
    public:
        static QHash<QString, QString>  enumOutputDevices();
        static bool                     changeOutputDevice( QString id );
    };
    
    #endif
    
  4. The implementation file (name it DefaultOutput.cpp):

    #include <stdio.h>
    #include <wchar.h>
    #include <tchar.h>
    #include "windows.h"
    #include "Mmdeviceapi.h"
    #include "PolicyConfig.h"
    #include "Propidl.h"
    #include "Functiondiscoverykeys_devpkey.h"
    #include "defaultoutput.h"
    
    QHash<QString, QString> DefaultOutput::enumOutputDevices()
    {
        QHash<QString, QString> list;
    
        try
        {
            HRESULT hr = CoInitialize( NULL );
            if( FAILED( hr ) )
                return list;
            IMMDeviceEnumerator *pEnum = NULL;
            // Create a multimedia device enumerator.
            hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), (void**)&pEnum );
            if( FAILED( hr ) )
                return list;
            IMMDeviceCollection *pDevices;
            // Enumerate the output devices.
            hr = pEnum->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &pDevices );
            if( FAILED( hr ) )
                return list;
            UINT count;
            pDevices->GetCount(&count);
            if( FAILED( hr ) )
                return list;
            for( UINT i = 0; i < count; i++ )
            {
                IMMDevice *pDevice;
                hr = pDevices->Item( i, &pDevice );
                if( SUCCEEDED( hr ) )
                {
                    LPWSTR wstrID = NULL;
                    hr = pDevice->GetId( &wstrID );
                    if( SUCCEEDED( hr ) )
                    {
                        IPropertyStore* pStore;
                        hr = pDevice->OpenPropertyStore( STGM_READ, &pStore );
                        if( SUCCEEDED( hr ) )
                        {
                            PROPVARIANT friendlyName;
                            PropVariantInit( &friendlyName );
                            hr = pStore->GetValue( PKEY_Device_FriendlyName, &friendlyName );
                            if( SUCCEEDED( hr ) )
                            {
                                QString qid = QString::fromStdU16String( (char16_t*)wstrID );
                                QString qname = QString::fromStdU16String( (char16_t*)friendlyName.pwszVal );
                                list[qid] = qname;
                                PropVariantClear( &friendlyName );
                            }
    
                            pStore->Release();
                        }
                    }
    
                    pDevice->Release();
                }
            }
    
            pDevices->Release();
            pEnum->Release();
            return list;
        }
        catch( ... )
        {
            return list;
        }
    }
    
    bool DefaultOutput::changeOutputDevice( QString id )
    {
        try
        {
            IPolicyConfigVista* pPolicyConfig;
            ERole reserved = eConsole;
            HRESULT hr = CoCreateInstance( __uuidof( CPolicyConfigVistaClient ), NULL, CLSCTX_ALL, __uuidof( IPolicyConfigVista ), (LPVOID*)&pPolicyConfig );
            if( SUCCEEDED( hr ) )
            {
                hr = pPolicyConfig->SetDefaultEndpoint( (PCWSTR)id.toStdU16String().data(), reserved );
                pPolicyConfig->Release();
                return SUCCEEDED( hr );
            }
            else
                return false;
        }
        catch( ... )
        {
            return false;
        }
    }
    
  5. Enumerate Usage example:

    QHash<QString, QString> devices = DefaultOutput::enumOutputDevices();
    for( QHash<QString, QString>::iterator iter = devices.begin(); iter != devices.end(); iter++ )
    {
        ui->devicesComboBox->addItem( iter.value(), iter.key() );
    }
    
  6. Set current default device example:

    int index = ui->devicesComboBox->currentIndex();
    QString deviceId = ui->devicesComboBox->itemData( index ).toString();
    DefaultOutput::changeOutputDevice( deviceId );
    
rubmz
  • 1,947
  • 5
  • 27
  • 49
0

In Qt 6 there is a member function in QMediaPlayer class void setAudioOutput(QAudioOutput *output). However in Qt 5 there is no such function and described methods are not always workable.

The goal may be achieved by programmatically selecting desired output using OS utils such as Pulse Audio on Linux.

QProcess::startDetached("pacmd", QStringList() << "set-default-sink" << "alsa_output.pci-0000_00_1b.0.analog-stereo");

The name of the output may find using command pacmd list-sinks

Mike
  • 519
  • 1
  • 4
  • 10