Change default audio device on Windows
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
BasedOnStyle: LLVM
|
BasedOnStyle: LLVM
|
||||||
AccessModifierOffset: -2
|
AccessModifierOffset: -2
|
||||||
AlignAfterOpenBracket: DontAlign
|
AlignAfterOpenBracket: DontAlign
|
||||||
AlignConsecutiveAssignments: AcrossComments
|
AlignConsecutiveAssignments: true
|
||||||
AlignOperands: Align
|
AlignOperands: Align
|
||||||
AllowAllArgumentsOnNextLine: false
|
AllowAllArgumentsOnNextLine: false
|
||||||
AllowAllConstructorInitializersOnNextLine: false
|
AllowAllConstructorInitializersOnNextLine: false
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ sunshine needs access to uinput to create mouse and gamepad events:
|
|||||||
- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server)
|
- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server)
|
||||||
- [Moonlight](https://github.com/moonlight-stream)
|
- [Moonlight](https://github.com/moonlight-stream)
|
||||||
- [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :)
|
- [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :)
|
||||||
|
- [Eretik](http://eretik.omegahg.com/) (For creating PolicyConfig.h, allowing me to change the default audio device on Windows programmatically)
|
||||||
|
|
||||||
## Application List:
|
## Application List:
|
||||||
- You can use Environment variables in place of values
|
- You can use Environment variables in place of values
|
||||||
|
|||||||
164
sunshine/platform/windows/PolicyConfig.h
Normal file
164
sunshine/platform/windows/PolicyConfig.h
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// PolicyConfig.h
|
||||||
|
// Undocumented COM-interface IPolicyConfig.
|
||||||
|
// Use for set default audio render endpoint
|
||||||
|
// @author EreTIk
|
||||||
|
// http://eretik.omegahg.com/
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __MINGW32__
|
||||||
|
#undef DEFINE_GUID
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||||
|
#else
|
||||||
|
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8);
|
||||||
|
DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
interface 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
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
interface 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(
|
||||||
|
PCWSTR wszDeviceId,
|
||||||
|
ERole eRole);
|
||||||
|
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
|
||||||
|
PCWSTR,
|
||||||
|
INT);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface 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
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
interface 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(
|
||||||
|
PCWSTR wszDeviceId,
|
||||||
|
ERole eRole);
|
||||||
|
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
|
||||||
|
PCWSTR,
|
||||||
|
INT); // not available on Windows 7, use method from IPolicyConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
@@ -10,10 +10,23 @@
|
|||||||
|
|
||||||
#include <synchapi.h>
|
#include <synchapi.h>
|
||||||
|
|
||||||
|
#define INITGUID
|
||||||
|
#include <propkeydef.h>
|
||||||
|
#undef INITGUID
|
||||||
|
|
||||||
#include "sunshine/config.h"
|
#include "sunshine/config.h"
|
||||||
#include "sunshine/main.h"
|
#include "sunshine/main.h"
|
||||||
#include "sunshine/platform/common.h"
|
#include "sunshine/platform/common.h"
|
||||||
|
|
||||||
|
// Must be the last included file
|
||||||
|
// clang-format off
|
||||||
|
#include "PolicyConfig.h"
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
|
||||||
|
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
|
||||||
|
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
|
||||||
|
|
||||||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||||||
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
||||||
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
||||||
@@ -21,6 +34,8 @@ const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
|
|||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
namespace platf::audio {
|
namespace platf::audio {
|
||||||
|
constexpr auto SAMPLE_RATE = 48000;
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
void Release(T *p) {
|
void Release(T *p) {
|
||||||
p->Release();
|
p->Release();
|
||||||
@@ -33,10 +48,14 @@ void co_task_free(T *p) {
|
|||||||
|
|
||||||
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
|
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
|
||||||
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
|
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
|
||||||
|
using collection_t = util::safe_ptr<IMMDeviceCollection, Release<IMMDeviceCollection>>;
|
||||||
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
|
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
|
||||||
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
|
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
|
||||||
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
|
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
|
||||||
|
using wstring_t = util::safe_ptr<WCHAR, co_task_free<WCHAR>>;
|
||||||
using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
|
using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
|
||||||
|
using policy_t = util::safe_ptr<IPolicyConfig, Release<IPolicyConfig>>;
|
||||||
|
using prop_t = util::safe_ptr<IPropertyStore, Release<IPropertyStore>>;
|
||||||
|
|
||||||
class co_init_t : public deinit_t {
|
class co_init_t : public deinit_t {
|
||||||
public:
|
public:
|
||||||
@@ -49,25 +68,69 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class prop_var_t {
|
||||||
|
public:
|
||||||
|
prop_var_t() {
|
||||||
|
PropVariantInit(&prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
~prop_var_t() {
|
||||||
|
PropVariantClear(&prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
PROPVARIANT prop;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||||
struct format_t {
|
struct format_t {
|
||||||
|
enum type_e : int {
|
||||||
|
none,
|
||||||
|
mono,
|
||||||
|
stereo,
|
||||||
|
surr51,
|
||||||
|
surr71,
|
||||||
|
} type;
|
||||||
|
|
||||||
std::string_view name;
|
std::string_view name;
|
||||||
int channels;
|
int channels;
|
||||||
int channel_mask;
|
int channel_mask;
|
||||||
} formats[] {
|
} formats[] {
|
||||||
{ "Stereo"sv,
|
{
|
||||||
2,
|
format_t::mono,
|
||||||
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT },
|
"Mono"sv,
|
||||||
{ "Mono"sv,
|
|
||||||
1,
|
1,
|
||||||
SPEAKER_FRONT_CENTER },
|
SPEAKER_FRONT_CENTER,
|
||||||
{ "Surround 5.1"sv,
|
},
|
||||||
|
{
|
||||||
|
format_t::stereo,
|
||||||
|
"Stereo"sv,
|
||||||
|
2,
|
||||||
|
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format_t::surr51,
|
||||||
|
"Surround 5.1"sv,
|
||||||
6,
|
6,
|
||||||
SPEAKER_FRONT_LEFT |
|
SPEAKER_FRONT_LEFT |
|
||||||
SPEAKER_FRONT_RIGHT |
|
SPEAKER_FRONT_RIGHT |
|
||||||
SPEAKER_FRONT_CENTER |
|
SPEAKER_FRONT_CENTER |
|
||||||
SPEAKER_LOW_FREQUENCY |
|
SPEAKER_LOW_FREQUENCY |
|
||||||
SPEAKER_BACK_LEFT |
|
SPEAKER_BACK_LEFT |
|
||||||
SPEAKER_BACK_RIGHT }
|
SPEAKER_BACK_RIGHT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format_t::surr71,
|
||||||
|
"Surround 7.1"sv,
|
||||||
|
8,
|
||||||
|
SPEAKER_FRONT_LEFT |
|
||||||
|
SPEAKER_FRONT_RIGHT |
|
||||||
|
SPEAKER_FRONT_CENTER |
|
||||||
|
SPEAKER_LOW_FREQUENCY |
|
||||||
|
SPEAKER_BACK_LEFT |
|
||||||
|
SPEAKER_BACK_RIGHT |
|
||||||
|
SPEAKER_SIDE_LEFT |
|
||||||
|
SPEAKER_SIDE_RIGHT,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
|
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
|
||||||
@@ -80,6 +143,32 @@ void set_wave_format(audio::wave_format_t &wave_format, const format_t &format)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) {
|
||||||
|
wave_format->wBitsPerSample = 16;
|
||||||
|
wave_format->nSamplesPerSec = sample_rate;
|
||||||
|
switch(wave_format->wFormatTag) {
|
||||||
|
case WAVE_FORMAT_PCM:
|
||||||
|
break;
|
||||||
|
case WAVE_FORMAT_IEEE_FLOAT:
|
||||||
|
break;
|
||||||
|
case WAVE_FORMAT_EXTENSIBLE: {
|
||||||
|
auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get();
|
||||||
|
if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
|
||||||
|
wave_ex->Samples.wValidBitsPerSample = 16;
|
||||||
|
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']';
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']';
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void surround51_to_stereo(std::vector<std::int16_t> &sample_in, const util::buffer_t<std::int16_t> &sample_out) {
|
void surround51_to_stereo(std::vector<std::int16_t> &sample_in, const util::buffer_t<std::int16_t> &sample_out) {
|
||||||
enum surround51_e : int {
|
enum surround51_e : int {
|
||||||
front_left,
|
front_left,
|
||||||
@@ -108,7 +197,6 @@ void surround51_to_stereo(std::vector<std::int16_t> &sample_in, const util::buff
|
|||||||
right += sample_out_p[low_frequency] * 30 / 100;
|
right += sample_out_p[low_frequency] * 30 / 100;
|
||||||
right += sample_out_p[back_left] * 30 / 100;
|
right += sample_out_p[back_left] * 30 / 100;
|
||||||
right += sample_out_p[back_right] * 70 / 100;
|
right += sample_out_p[back_right] * 70 / 100;
|
||||||
;
|
|
||||||
|
|
||||||
*sample_in_pos++ = (std::uint16_t)left;
|
*sample_in_pos++ = (std::uint16_t)left;
|
||||||
*sample_in_pos++ = (std::uint16_t)right;
|
*sample_in_pos++ = (std::uint16_t)right;
|
||||||
@@ -141,35 +229,15 @@ audio_client_t make_audio_client(device_t &device, const format_t &format, int s
|
|||||||
|
|
||||||
wave_format_t wave_format;
|
wave_format_t wave_format;
|
||||||
status = audio_client->GetMixFormat(&wave_format);
|
status = audio_client->GetMixFormat(&wave_format);
|
||||||
|
|
||||||
if(FAILED(status)) {
|
if(FAILED(status)) {
|
||||||
BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
wave_format->wBitsPerSample = 16;
|
if(init_wave_format(wave_format, sample_rate)) {
|
||||||
wave_format->nSamplesPerSec = sample_rate;
|
|
||||||
switch(wave_format->wFormatTag) {
|
|
||||||
case WAVE_FORMAT_PCM:
|
|
||||||
break;
|
|
||||||
case WAVE_FORMAT_IEEE_FLOAT:
|
|
||||||
break;
|
|
||||||
case WAVE_FORMAT_EXTENSIBLE: {
|
|
||||||
auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get();
|
|
||||||
if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
|
|
||||||
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
|
||||||
wave_ex->Samples.wValidBitsPerSample = 16;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']';
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']';
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
};
|
}
|
||||||
|
|
||||||
set_wave_format(wave_format, format);
|
set_wave_format(wave_format, format);
|
||||||
|
|
||||||
status = audio_client->Initialize(
|
status = audio_client->Initialize(
|
||||||
@@ -187,6 +255,43 @@ audio_client_t make_audio_client(device_t &device, const format_t &format, int s
|
|||||||
return audio_client;
|
return audio_client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wchar_t *no_null(const wchar_t *str) {
|
||||||
|
return str ? str : L"Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
format_t::type_e validate_device(device_t &device) {
|
||||||
|
for(const auto &format : formats) {
|
||||||
|
// Ensure WaveFromat is compatible
|
||||||
|
auto audio_client = make_audio_client(device, format, SAMPLE_RATE);
|
||||||
|
|
||||||
|
BOOST_LOG(debug) << format.name << ": "sv << !audio_client ? "unsupported"sv : "supported"sv;
|
||||||
|
|
||||||
|
if(audio_client) {
|
||||||
|
return format.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return format_t::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_t default_device(device_enum_t &device_enum) {
|
||||||
|
device_t device;
|
||||||
|
HRESULT status;
|
||||||
|
status = device_enum->GetDefaultAudioEndpoint(
|
||||||
|
eRender,
|
||||||
|
eConsole,
|
||||||
|
&device);
|
||||||
|
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
class mic_wasapi_t : public mic_t {
|
class mic_wasapi_t : public mic_t {
|
||||||
public:
|
public:
|
||||||
capture_e sample(std::vector<std::int16_t> &sample_in) override {
|
capture_e sample(std::vector<std::int16_t> &sample_in) override {
|
||||||
@@ -246,22 +351,8 @@ public:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config::audio.sink.empty()) {
|
auto device = default_device(device_enum);
|
||||||
status = device_enum->GetDefaultAudioEndpoint(
|
if(!device) {
|
||||||
eRender,
|
|
||||||
eConsole,
|
|
||||||
&device);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
|
||||||
auto wstring_device_id = converter.from_bytes(config::audio.sink);
|
|
||||||
|
|
||||||
status = device_enum->GetDevice(wstring_device_id.c_str(), &device);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(FAILED(status)) {
|
|
||||||
BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']';
|
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,6 +507,210 @@ public:
|
|||||||
|
|
||||||
format_t *format;
|
format_t *format;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class audio_control_t : public ::platf::audio_control_t {
|
||||||
|
public:
|
||||||
|
std::optional<sink_t> sink_info() override {
|
||||||
|
auto virtual_adapter_name = L"Steam Streaming Speakers"sv;
|
||||||
|
|
||||||
|
sink_t sink;
|
||||||
|
|
||||||
|
audio::device_enum_t device_enum;
|
||||||
|
auto status = CoCreateInstance(
|
||||||
|
CLSID_MMDeviceEnumerator,
|
||||||
|
nullptr,
|
||||||
|
CLSCTX_ALL,
|
||||||
|
IID_IMMDeviceEnumerator,
|
||||||
|
(void **)&device_enum);
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto device = default_device(device_enum);
|
||||||
|
if(!device) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::wstring_t wstring;
|
||||||
|
device->GetId(&wstring);
|
||||||
|
|
||||||
|
sink.host = converter.to_bytes(wstring.get());
|
||||||
|
|
||||||
|
collection_t collection;
|
||||||
|
status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT count;
|
||||||
|
collection->GetCount(&count);
|
||||||
|
|
||||||
|
std::string virtual_device_id;
|
||||||
|
BOOST_LOG(debug) << "====== Found "sv << count << " potential audio devices ======"sv;
|
||||||
|
for(auto x = 0; x < count; ++x) {
|
||||||
|
audio::device_t device;
|
||||||
|
collection->Item(x, &device);
|
||||||
|
|
||||||
|
auto type = validate_device(device);
|
||||||
|
if(type == format_t::none) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::wstring_t wstring;
|
||||||
|
device->GetId(&wstring);
|
||||||
|
|
||||||
|
audio::prop_t prop;
|
||||||
|
device->OpenPropertyStore(STGM_READ, &prop);
|
||||||
|
|
||||||
|
prop_var_t adapter_friendly_name;
|
||||||
|
prop_var_t device_friendly_name;
|
||||||
|
prop_var_t device_desc;
|
||||||
|
|
||||||
|
prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop);
|
||||||
|
prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop);
|
||||||
|
prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop);
|
||||||
|
|
||||||
|
auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal);
|
||||||
|
BOOST_LOG(debug)
|
||||||
|
<< L"===== Device ====="sv << std::endl
|
||||||
|
<< L"Device ID : "sv << wstring.get() << std::endl
|
||||||
|
<< L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl
|
||||||
|
<< L"Adapter name : "sv << adapter_name << std::endl
|
||||||
|
<< L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) {
|
||||||
|
virtual_device_id = converter.to_bytes(wstring.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!virtual_device_id.empty()) {
|
||||||
|
sink.null = std::make_optional(sink_t::null_t {
|
||||||
|
"virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id,
|
||||||
|
"virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id,
|
||||||
|
"virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return sink;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
|
||||||
|
auto mic = std::make_unique<mic_wasapi_t>();
|
||||||
|
|
||||||
|
if(mic->init(sample_rate, frame_size)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the requested sink is a virtual sink, meaning no speakers attached to
|
||||||
|
* the host, then we can seamlessly set the format to stereo and surround sound.
|
||||||
|
*
|
||||||
|
* Any virtual sink detected will be prefixed by:
|
||||||
|
* virtual-(format name)
|
||||||
|
* If it doesn't contain that prefix, then the format will not be changed
|
||||||
|
*/
|
||||||
|
std::optional<std::wstring> set_format(const std::string &sink) {
|
||||||
|
std::string_view sv { sink.c_str(), sink.size() };
|
||||||
|
|
||||||
|
format_t::type_e type = format_t::none;
|
||||||
|
// sink format:
|
||||||
|
// [virtual-(format name)]device_id
|
||||||
|
auto prefix = "virtual-"sv;
|
||||||
|
if(sv.find(prefix) == 0) {
|
||||||
|
sv = sv.substr(prefix.size(), sv.size() - prefix.size());
|
||||||
|
|
||||||
|
for(auto &format : formats) {
|
||||||
|
auto &name = format.name;
|
||||||
|
if(sv.find(name) == 0) {
|
||||||
|
type = format.type;
|
||||||
|
sv = sv.substr(name.size(), sv.size() - name.size());
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto wstring_device_id = converter.from_bytes(sv.data());
|
||||||
|
|
||||||
|
if(type == format_t::none) {
|
||||||
|
// wstring_device_id does not contain virtual-(format name)
|
||||||
|
// It's a simple deviceId, just pass it back
|
||||||
|
return std::make_optional(std::move(wstring_device_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
wave_format_t wave_format;
|
||||||
|
auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format);
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(init_wave_format(wave_format, SAMPLE_RATE)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
set_wave_format(wave_format, formats[(int)type - 1]);
|
||||||
|
|
||||||
|
WAVEFORMATEX p { *wave_format.get() };
|
||||||
|
status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), &p);
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_optional(std::move(wstring_device_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_sink(const std::string &sink) override {
|
||||||
|
auto wstring_device_id = set_format(sink);
|
||||||
|
if(!wstring_device_id) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int failure {};
|
||||||
|
for(int x = 0; x < (int)ERole_enum_count; ++x) {
|
||||||
|
auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x);
|
||||||
|
if(status) {
|
||||||
|
BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']';
|
||||||
|
|
||||||
|
++failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
int init() {
|
||||||
|
auto status = CoCreateInstance(
|
||||||
|
CLSID_CPolicyConfigClient,
|
||||||
|
nullptr,
|
||||||
|
CLSCTX_ALL,
|
||||||
|
IID_IPolicyConfig,
|
||||||
|
(void **)&policy);
|
||||||
|
|
||||||
|
if(FAILED(status)) {
|
||||||
|
BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~audio_control_t() override {}
|
||||||
|
|
||||||
|
policy_t policy;
|
||||||
|
};
|
||||||
} // namespace platf::audio
|
} // namespace platf::audio
|
||||||
|
|
||||||
namespace platf {
|
namespace platf {
|
||||||
@@ -425,14 +720,14 @@ namespace dxgi {
|
|||||||
int init();
|
int init();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate, std::uint32_t frame_size) {
|
std::unique_ptr<audio_control_t> audio_control() {
|
||||||
auto mic = std::make_unique<audio::mic_wasapi_t>();
|
auto control = std::make_unique<audio::audio_control_t>();
|
||||||
|
|
||||||
if(mic->init(sample_rate, frame_size)) {
|
if(control->init() || control->set_sink("virtual-Stereo{0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}"s)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mic;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<deinit_t> init() {
|
std::unique_ptr<deinit_t> init() {
|
||||||
|
|||||||
@@ -87,7 +87,17 @@ struct format_t {
|
|||||||
SPEAKER_FRONT_CENTER |
|
SPEAKER_FRONT_CENTER |
|
||||||
SPEAKER_LOW_FREQUENCY |
|
SPEAKER_LOW_FREQUENCY |
|
||||||
SPEAKER_BACK_LEFT |
|
SPEAKER_BACK_LEFT |
|
||||||
SPEAKER_BACK_RIGHT }
|
SPEAKER_BACK_RIGHT },
|
||||||
|
{ "Surround 7.1"sv,
|
||||||
|
8,
|
||||||
|
SPEAKER_FRONT_LEFT |
|
||||||
|
SPEAKER_FRONT_RIGHT |
|
||||||
|
SPEAKER_FRONT_CENTER |
|
||||||
|
SPEAKER_LOW_FREQUENCY |
|
||||||
|
SPEAKER_BACK_LEFT |
|
||||||
|
SPEAKER_BACK_RIGHT |
|
||||||
|
SPEAKER_SIDE_LEFT |
|
||||||
|
SPEAKER_SIDE_RIGHT }
|
||||||
};
|
};
|
||||||
|
|
||||||
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
|
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
|
||||||
@@ -285,7 +295,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
audio::collection_t collection;
|
audio::collection_t collection;
|
||||||
status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &collection);
|
status = device_enum->EnumAudioEndpoints(eRender, device_state_filter, &collection);
|
||||||
|
|
||||||
if(FAILED(status)) {
|
if(FAILED(status)) {
|
||||||
std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||||
@@ -296,7 +306,7 @@ int main(int argc, char *argv[]) {
|
|||||||
UINT count;
|
UINT count;
|
||||||
collection->GetCount(&count);
|
collection->GetCount(&count);
|
||||||
|
|
||||||
std::cout << "====== Found "sv << count << " potential audio devices ======"sv << std::endl;
|
std::cout << "====== Found "sv << count << " audio devices ======"sv << std::endl;
|
||||||
for(auto x = 0; x < count; ++x) {
|
for(auto x = 0; x < count; ++x) {
|
||||||
audio::device_t device;
|
audio::device_t device;
|
||||||
collection->Item(x, &device);
|
collection->Item(x, &device);
|
||||||
|
|||||||
Reference in New Issue
Block a user