Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mpd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Иван Мажукин
mpd
Commits
93d87854
Unverified
Commit
93d87854
authored
May 30, 2020
by
Shen-Ta Hsieh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
src/output: add wasapi output and mixer plugin
parent
e5eac71d
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1025 additions
and
2 deletions
+1025
-2
MixerList.hxx
src/mixer/MixerList.hxx
+1
-0
WasapiMixerPlugin.cxx
src/mixer/plugins/WasapiMixerPlugin.cxx
+128
-0
meson.build
src/mixer/plugins/meson.build
+4
-1
Registry.cxx
src/output/Registry.cxx
+8
-0
WasapiOutputPlugin.cxx
src/output/plugins/WasapiOutputPlugin.cxx
+824
-0
WasapiOutputPlugin.hxx
src/output/plugins/WasapiOutputPlugin.hxx
+44
-0
meson.build
src/output/plugins/meson.build
+16
-1
No files found.
src/mixer/MixerList.hxx
View file @
93d87854
...
...
@@ -36,6 +36,7 @@ extern const MixerPlugin oss_mixer_plugin;
extern
const
MixerPlugin
osx_mixer_plugin
;
extern
const
MixerPlugin
pulse_mixer_plugin
;
extern
const
MixerPlugin
winmm_mixer_plugin
;
extern
const
MixerPlugin
wasapi_mixer_plugin
;
extern
const
MixerPlugin
sndio_mixer_plugin
;
#endif
src/mixer/plugins/WasapiMixerPlugin.cxx
0 → 100644
View file @
93d87854
/*
* Copyright 2020 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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 "mixer/MixerInternal.hxx"
#include "output/plugins/WasapiOutputPlugin.hxx"
#include "win32/Com.hxx"
#include "win32/HResult.hxx"
#include <cmath>
#include <endpointvolume.h>
#include <optional>
class
WasapiMixer
final
:
public
Mixer
{
WasapiOutput
&
output
;
std
::
optional
<
COM
>
com
;
public
:
WasapiMixer
(
WasapiOutput
&
_output
,
MixerListener
&
_listener
)
:
Mixer
(
wasapi_mixer_plugin
,
_listener
),
output
(
_output
)
{}
void
Open
()
override
{
com
.
emplace
();
}
void
Close
()
noexcept
override
{
com
.
reset
();
}
int
GetVolume
()
override
{
HRESULT
result
;
float
volume_level
;
if
(
wasapi_is_exclusive
(
output
))
{
ComPtr
<
IAudioEndpointVolume
>
endpoint_volume
;
result
=
wasapi_output_get_device
(
output
)
->
Activate
(
__uuidof
(
IAudioEndpointVolume
),
CLSCTX_ALL
,
nullptr
,
endpoint_volume
.
AddressCast
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get device endpoint volume"
);
}
result
=
endpoint_volume
->
GetMasterVolumeLevelScalar
(
&
volume_level
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get master volume level"
);
}
}
else
{
ComPtr
<
ISimpleAudioVolume
>
session_volume
;
result
=
wasapi_output_get_client
(
output
)
->
GetService
(
__uuidof
(
ISimpleAudioVolume
),
session_volume
.
AddressCast
<
void
>
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get client session volume"
);
}
result
=
session_volume
->
GetMasterVolume
(
&
volume_level
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get master volume"
);
}
}
return
std
::
lround
(
volume_level
*
100.0
f
);
}
void
SetVolume
(
unsigned
volume
)
override
{
HRESULT
result
;
const
float
volume_level
=
volume
/
100.0
f
;
if
(
wasapi_is_exclusive
(
output
))
{
ComPtr
<
IAudioEndpointVolume
>
endpoint_volume
;
result
=
wasapi_output_get_device
(
output
)
->
Activate
(
__uuidof
(
IAudioEndpointVolume
),
CLSCTX_ALL
,
nullptr
,
endpoint_volume
.
AddressCast
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get device endpoint volume"
);
}
result
=
endpoint_volume
->
SetMasterVolumeLevelScalar
(
volume_level
,
nullptr
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to set master volume level"
);
}
}
else
{
ComPtr
<
ISimpleAudioVolume
>
session_volume
;
result
=
wasapi_output_get_client
(
output
)
->
GetService
(
__uuidof
(
ISimpleAudioVolume
),
session_volume
.
AddressCast
<
void
>
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get client session volume"
);
}
result
=
session_volume
->
SetMasterVolume
(
volume_level
,
nullptr
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to set master volume"
);
}
}
}
};
static
Mixer
*
wasapi_mixer_init
(
EventLoop
&
,
AudioOutput
&
ao
,
MixerListener
&
listener
,
const
ConfigBlock
&
)
{
return
new
WasapiMixer
(
wasapi_output_downcast
(
ao
),
listener
);
}
const
MixerPlugin
wasapi_mixer_plugin
=
{
wasapi_mixer_init
,
false
,
};
src/mixer/plugins/meson.build
View file @
93d87854
...
...
@@ -31,7 +31,10 @@ if libsndio_dep.found()
endif
if is_windows
mixer_plugins_sources += 'WinmmMixerPlugin.cxx'
mixer_plugins_sources += [
'WinmmMixerPlugin.cxx',
'WasapiMixerPlugin.cxx',
]
endif
if is_android
...
...
src/output/Registry.cxx
View file @
93d87854
...
...
@@ -38,7 +38,12 @@
#include "plugins/ShoutOutputPlugin.hxx"
#include "plugins/sles/SlesOutputPlugin.hxx"
#include "plugins/SolarisOutputPlugin.hxx"
#ifdef ENABLE_WINMM_OUTPUT
#include "plugins/WinmmOutputPlugin.hxx"
#endif
#ifdef ENABLE_WASAPI_OUTPUT
#include "plugins/WasapiOutputPlugin.hxx"
#endif
#include "util/StringAPI.hxx"
const
AudioOutputPlugin
*
const
audio_output_plugins
[]
=
{
...
...
@@ -94,6 +99,9 @@ const AudioOutputPlugin *const audio_output_plugins[] = {
#ifdef ENABLE_WINMM_OUTPUT
&
winmm_output_plugin
,
#endif
#ifdef ENABLE_WASAPI_OUTPUT
&
wasapi_output_plugin
,
#endif
nullptr
};
...
...
src/output/plugins/WasapiOutputPlugin.cxx
0 → 100644
View file @
93d87854
/*
* Copyright 2020 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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 <initguid.h>
#include "Log.hxx"
#include "WasapiOutputPlugin.hxx"
#include "lib/icu/Win32.hxx"
#include "mixer/MixerList.hxx"
#include "thread/Cond.hxx"
#include "thread/Mutex.hxx"
#include "thread/Thread.hxx"
#include "util/AllocatedString.hxx"
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx"
#include "win32/Com.hxx"
#include "win32/ComHeapPtr.hxx"
#include "win32/HResult.hxx"
#include "win32/WinEvent.hxx"
#include <algorithm>
#include <boost/lockfree/spsc_queue.hpp>
#include <cinttypes>
#include <cmath>
#include <functiondiscoverykeys_devpkey.h>
#include <optional>
#include <variant>
namespace
{
static
constexpr
Domain
wasapi_output_domain
(
"wasapi_output"
);
gcc_const
constexpr
uint32_t
GetChannelMask
(
const
uint8_t
channels
)
noexcept
{
switch
(
channels
)
{
case
1
:
return
KSAUDIO_SPEAKER_MONO
;
case
2
:
return
KSAUDIO_SPEAKER_STEREO
;
case
3
:
return
SPEAKER_FRONT_LEFT
|
SPEAKER_FRONT_RIGHT
|
SPEAKER_FRONT_CENTER
;
case
4
:
return
KSAUDIO_SPEAKER_QUAD
;
case
5
:
return
SPEAKER_FRONT_LEFT
|
SPEAKER_FRONT_RIGHT
|
SPEAKER_FRONT_CENTER
|
SPEAKER_BACK_LEFT
|
SPEAKER_BACK_RIGHT
;
case
6
:
return
KSAUDIO_SPEAKER_5POINT1
;
case
7
:
return
KSAUDIO_SPEAKER_5POINT1
|
SPEAKER_BACK_CENTER
;
case
8
:
return
KSAUDIO_SPEAKER_7POINT1_SURROUND
;
default:
gcc_unreachable
();
}
}
template
<
typename
Functor
>
inline
bool
SafeTry
(
Functor
&&
functor
)
{
try
{
functor
();
return
true
;
}
catch
(
std
::
runtime_error
&
err
)
{
FormatError
(
wasapi_output_domain
,
"%s"
,
err
.
what
());
return
false
;
}
}
template
<
typename
Functor
>
inline
bool
SafeSilenceTry
(
Functor
&&
functor
)
{
try
{
functor
();
return
true
;
}
catch
(
std
::
runtime_error
&
err
)
{
return
false
;
}
}
inline
void
SetFormat
(
WAVEFORMATEXTENSIBLE
&
device_format
,
const
AudioFormat
&
audio_format
)
noexcept
{
device_format
.
dwChannelMask
=
GetChannelMask
(
audio_format
.
channels
);
device_format
.
Format
.
wFormatTag
=
WAVE_FORMAT_EXTENSIBLE
;
device_format
.
Format
.
nChannels
=
audio_format
.
channels
;
device_format
.
Format
.
nSamplesPerSec
=
audio_format
.
sample_rate
;
device_format
.
Format
.
nBlockAlign
=
audio_format
.
GetFrameSize
();
device_format
.
Format
.
nAvgBytesPerSec
=
audio_format
.
sample_rate
*
audio_format
.
GetFrameSize
();
device_format
.
Format
.
wBitsPerSample
=
audio_format
.
GetSampleSize
()
*
8
;
device_format
.
Format
.
cbSize
=
sizeof
(
WAVEFORMATEXTENSIBLE
)
-
sizeof
(
WAVEFORMATEX
);
device_format
.
Samples
.
wValidBitsPerSample
=
audio_format
.
GetSampleSize
()
*
8
;
if
(
audio_format
.
format
==
SampleFormat
::
FLOAT
)
{
device_format
.
SubFormat
=
KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
}
else
{
device_format
.
SubFormat
=
KSDATAFORMAT_SUBTYPE_PCM
;
}
}
inline
constexpr
const
unsigned
int
kErrorId
=
-
1
;
}
// namespace
class
WasapiOutputThread
:
public
Thread
{
public
:
enum
class
Status
:
uint32_t
{
FINISH
,
PLAY
,
PAUSE
};
WasapiOutputThread
(
std
::
shared_ptr
<
WinEvent
>
_event
,
ComPtr
<
IAudioClient
>
_client
,
ComPtr
<
IAudioRenderClient
>
&&
_render_client
,
const
UINT32
_frame_size
,
const
UINT32
_buffer_size_in_frames
,
bool
_is_exclusive
,
boost
::
lockfree
::
spsc_queue
<
BYTE
>
&
_spsc_buffer
)
:
Thread
(
BIND_THIS_METHOD
(
Work
)),
event
(
std
::
move
(
_event
)),
client
(
std
::
move
(
_client
)),
render_client
(
std
::
move
(
_render_client
)),
frame_size
(
_frame_size
),
buffer_size_in_frames
(
_buffer_size_in_frames
),
is_exclusive
(
_is_exclusive
),
spsc_buffer
(
_spsc_buffer
)
{}
void
Finish
()
noexcept
{
return
SetStatus
(
Status
::
FINISH
);
}
void
Play
()
noexcept
{
return
SetStatus
(
Status
::
PLAY
);
}
void
Pause
()
noexcept
{
return
SetStatus
(
Status
::
PAUSE
);
}
void
WaitWrite
()
noexcept
{
std
::
unique_lock
<
Mutex
>
lock
(
write
.
mutex
);
write
.
cond
.
wait
(
lock
);
}
void
CheckException
()
{
std
::
unique_lock
<
Mutex
>
lock
(
error
.
mutex
);
if
(
error
.
error_ptr
)
{
std
::
exception_ptr
err
=
std
::
exchange
(
error
.
error_ptr
,
nullptr
);
error
.
cond
.
notify_all
();
std
::
rethrow_exception
(
err
);
}
}
private
:
std
::
shared_ptr
<
WinEvent
>
event
;
std
::
optional
<
COM
>
com
;
ComPtr
<
IAudioClient
>
client
;
ComPtr
<
IAudioRenderClient
>
render_client
;
const
UINT32
frame_size
;
const
UINT32
buffer_size_in_frames
;
bool
is_exclusive
;
boost
::
lockfree
::
spsc_queue
<
BYTE
>
&
spsc_buffer
;
alignas
(
BOOST_LOCKFREE_CACHELINE_BYTES
)
std
::
atomic
<
Status
>
status
=
Status
::
PAUSE
;
alignas
(
BOOST_LOCKFREE_CACHELINE_BYTES
)
struct
{
Mutex
mutex
;
Cond
cond
;
}
write
{};
alignas
(
BOOST_LOCKFREE_CACHELINE_BYTES
)
struct
{
Mutex
mutex
;
Cond
cond
;
std
::
exception_ptr
error_ptr
=
nullptr
;
}
error
{};
void
SetStatus
(
Status
s
)
noexcept
{
status
.
store
(
s
);
event
->
Set
();
}
void
Work
()
noexcept
;
};
class
WasapiOutput
final
:
public
AudioOutput
{
public
:
static
AudioOutput
*
Create
(
EventLoop
&
,
const
ConfigBlock
&
block
);
WasapiOutput
(
const
ConfigBlock
&
block
);
void
Enable
()
override
;
void
Disable
()
noexcept
override
;
void
Open
(
AudioFormat
&
audio_format
)
override
;
void
Close
()
noexcept
override
;
std
::
chrono
::
steady_clock
::
duration
Delay
()
const
noexcept
override
;
size_t
Play
(
const
void
*
chunk
,
size_t
size
)
override
;
void
Drain
()
override
;
bool
Pause
()
override
;
constexpr
bool
Exclusive
()
const
{
return
is_exclusive
;
}
constexpr
size_t
FrameSize
()
const
{
return
device_format
.
Format
.
nBlockAlign
;
}
constexpr
size_t
SampleRate
()
const
{
return
device_format
.
Format
.
nSamplesPerSec
;
}
private
:
bool
is_started
=
false
;
bool
is_exclusive
;
bool
enumerate_devices
;
std
::
string
device_config
;
std
::
vector
<
std
::
pair
<
unsigned
int
,
AllocatedString
<
char
>>>
device_desc
;
std
::
shared_ptr
<
WinEvent
>
event
;
std
::
optional
<
COM
>
com
;
ComPtr
<
IMMDeviceEnumerator
>
enumerator
;
ComPtr
<
IMMDevice
>
device
;
ComPtr
<
IAudioClient
>
client
;
WAVEFORMATEXTENSIBLE
device_format
;
std
::
unique_ptr
<
WasapiOutputThread
>
thread
;
std
::
unique_ptr
<
boost
::
lockfree
::
spsc_queue
<
BYTE
>>
spsc_buffer
;
std
::
size_t
watermark
;
friend
bool
wasapi_is_exclusive
(
WasapiOutput
&
output
)
noexcept
;
friend
IMMDevice
*
wasapi_output_get_device
(
WasapiOutput
&
output
)
noexcept
;
friend
IAudioClient
*
wasapi_output_get_client
(
WasapiOutput
&
output
)
noexcept
;
void
FindExclusiveFormatSupported
(
AudioFormat
&
audio_format
);
void
FindSharedFormatSupported
(
AudioFormat
&
audio_format
);
void
EnumerateDevices
();
void
GetDevice
(
unsigned
int
index
);
unsigned
int
SearchDevice
(
std
::
string_view
name
);
void
GetDefaultDevice
();
};
WasapiOutput
&
wasapi_output_downcast
(
AudioOutput
&
output
)
noexcept
{
return
static_cast
<
WasapiOutput
&>
(
output
);
}
bool
wasapi_is_exclusive
(
WasapiOutput
&
output
)
noexcept
{
return
output
.
is_exclusive
;
}
IMMDevice
*
wasapi_output_get_device
(
WasapiOutput
&
output
)
noexcept
{
return
output
.
device
.
get
();
}
IAudioClient
*
wasapi_output_get_client
(
WasapiOutput
&
output
)
noexcept
{
return
output
.
client
.
get
();
}
void
WasapiOutputThread
::
Work
()
noexcept
{
FormatDebug
(
wasapi_output_domain
,
"Working thread started"
);
try
{
com
.
emplace
();
}
catch
(...)
{
std
::
unique_lock
<
Mutex
>
lock
(
error
.
mutex
);
error
.
error_ptr
=
std
::
current_exception
();
error
.
cond
.
wait
(
lock
);
assert
(
error
.
error_ptr
==
nullptr
);
return
;
}
while
(
true
)
{
try
{
event
->
Wait
(
INFINITE
);
Status
current_state
=
status
.
load
();
if
(
current_state
==
Status
::
FINISH
)
{
FormatDebug
(
wasapi_output_domain
,
"Working thread stopped"
);
return
;
}
AtScopeExit
(
&
)
{
write
.
cond
.
notify_all
();
};
HRESULT
result
;
UINT32
data_in_frames
;
result
=
client
->
GetCurrentPadding
(
&
data_in_frames
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Failed to get current padding"
);
}
UINT32
write_in_frames
=
buffer_size_in_frames
;
if
(
!
is_exclusive
)
{
if
(
data_in_frames
>=
buffer_size_in_frames
)
{
continue
;
}
write_in_frames
-=
data_in_frames
;
}
else
if
(
data_in_frames
>=
buffer_size_in_frames
*
2
)
{
continue
;
}
BYTE
*
data
;
result
=
render_client
->
GetBuffer
(
write_in_frames
,
&
data
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Failed to get buffer"
);
}
AtScopeExit
(
&
)
{
render_client
->
ReleaseBuffer
(
write_in_frames
,
0
);
};
const
UINT32
write_size
=
write_in_frames
*
frame_size
;
UINT32
new_data_size
=
0
;
if
(
current_state
==
Status
::
PLAY
)
{
new_data_size
=
spsc_buffer
.
pop
(
data
,
write_size
);
}
else
{
FormatDebug
(
wasapi_output_domain
,
"Working thread paused"
);
}
std
::
fill_n
(
data
+
new_data_size
,
write_size
-
new_data_size
,
0
);
}
catch
(...)
{
std
::
unique_lock
<
Mutex
>
lock
(
error
.
mutex
);
error
.
error_ptr
=
std
::
current_exception
();
error
.
cond
.
wait
(
lock
);
assert
(
error
.
error_ptr
==
nullptr
);
}
}
}
AudioOutput
*
WasapiOutput
::
Create
(
EventLoop
&
,
const
ConfigBlock
&
block
)
{
return
new
WasapiOutput
(
block
);
}
WasapiOutput
::
WasapiOutput
(
const
ConfigBlock
&
block
)
:
AudioOutput
(
FLAG_ENABLE_DISABLE
|
FLAG_PAUSE
),
is_exclusive
(
block
.
GetBlockValue
(
"exclusive"
,
false
)),
enumerate_devices
(
block
.
GetBlockValue
(
"enumerate"
,
false
)),
device_config
(
block
.
GetBlockValue
(
"device"
,
""
))
{}
void
WasapiOutput
::
Enable
()
{
com
.
emplace
();
event
=
std
::
make_shared
<
WinEvent
>
();
enumerator
.
CoCreateInstance
(
__uuidof
(
MMDeviceEnumerator
),
nullptr
,
CLSCTX_INPROC_SERVER
);
device_desc
.
clear
();
device
.
reset
();
if
(
enumerate_devices
&&
SafeTry
([
this
]()
{
EnumerateDevices
();
}))
{
for
(
const
auto
&
desc
:
device_desc
)
{
FormatDefault
(
wasapi_output_domain
,
"Device
\"
%u
\"
\"
%s
\"
"
,
desc
.
first
,
desc
.
second
.
c_str
());
}
}
unsigned
int
id
=
kErrorId
;
if
(
!
device_config
.
empty
())
{
if
(
!
SafeSilenceTry
([
this
,
&
id
]()
{
id
=
std
::
stoul
(
device_config
);
}))
{
id
=
SearchDevice
(
device_config
);
}
}
if
(
id
!=
kErrorId
)
{
SafeTry
([
this
,
id
]()
{
GetDevice
(
id
);
});
}
if
(
!
device
)
{
GetDefaultDevice
();
}
device_desc
.
clear
();
}
void
WasapiOutput
::
Disable
()
noexcept
{
if
(
thread
)
{
try
{
thread
->
Finish
();
thread
->
Join
();
}
catch
(
std
::
exception
&
err
)
{
FormatError
(
wasapi_output_domain
,
"exception while disabling: %s"
,
err
.
what
());
}
thread
.
reset
();
spsc_buffer
.
reset
();
client
.
reset
();
}
device
.
reset
();
enumerator
.
reset
();
com
.
reset
();
event
.
reset
();
}
void
WasapiOutput
::
Open
(
AudioFormat
&
audio_format
)
{
if
(
audio_format
.
channels
==
0
)
{
throw
FormatInvalidArgument
(
"channels should > 0"
);
}
client
.
reset
();
HRESULT
result
;
result
=
device
->
Activate
(
__uuidof
(
IAudioClient
),
CLSCTX_ALL
,
nullptr
,
client
.
AddressCast
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to activate audio client"
);
}
if
(
audio_format
.
format
==
SampleFormat
::
S24_P32
)
{
audio_format
.
format
=
SampleFormat
::
S32
;
}
if
(
audio_format
.
channels
>
8
)
{
audio_format
.
channels
=
8
;
}
if
(
Exclusive
())
{
FindExclusiveFormatSupported
(
audio_format
);
}
else
{
FindSharedFormatSupported
(
audio_format
);
}
using
s
=
std
::
chrono
::
seconds
;
using
ms
=
std
::
chrono
::
milliseconds
;
using
ns
=
std
::
chrono
::
nanoseconds
;
using
hundred_ns
=
std
::
chrono
::
duration
<
uint64_t
,
std
::
ratio
<
1
,
10000000
>>
;
// The unit in REFERENCE_TIME is hundred nanoseconds
REFERENCE_TIME
device_period
;
result
=
client
->
GetDevicePeriod
(
&
device_period
,
nullptr
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get device period"
);
}
FormatDebug
(
wasapi_output_domain
,
"Device period: %I64u ns"
,
size_t
(
ns
(
hundred_ns
(
device_period
)).
count
()));
REFERENCE_TIME
buffer_duration
=
device_period
;
if
(
!
Exclusive
())
{
const
REFERENCE_TIME
align
=
hundred_ns
(
ms
(
50
)).
count
();
buffer_duration
=
(
align
/
device_period
)
*
device_period
;
}
FormatDebug
(
wasapi_output_domain
,
"Buffer duration: %I64u ns"
,
size_t
(
ns
(
hundred_ns
(
buffer_duration
)).
count
()));
if
(
Exclusive
())
{
result
=
client
->
Initialize
(
AUDCLNT_SHAREMODE_EXCLUSIVE
,
AUDCLNT_STREAMFLAGS_NOPERSIST
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
buffer_duration
,
buffer_duration
,
reinterpret_cast
<
WAVEFORMATEX
*>
(
&
device_format
),
nullptr
);
if
(
result
==
AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED
)
{
// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
UINT32
buffer_size_in_frames
=
0
;
result
=
client
->
GetBufferSize
(
&
buffer_size_in_frames
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get audio client buffer size"
);
}
buffer_duration
=
std
::
ceil
(
double
(
buffer_size_in_frames
*
hundred_ns
(
s
(
1
)).
count
())
/
SampleRate
());
FormatDebug
(
wasapi_output_domain
,
"Aligned buffer duration: %I64u ns"
,
size_t
(
ns
(
hundred_ns
(
buffer_duration
)).
count
()));
client
.
reset
();
result
=
device
->
Activate
(
__uuidof
(
IAudioClient
),
CLSCTX_ALL
,
nullptr
,
client
.
AddressCast
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to activate audio client"
);
}
result
=
client
->
Initialize
(
AUDCLNT_SHAREMODE_EXCLUSIVE
,
AUDCLNT_STREAMFLAGS_NOPERSIST
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
buffer_duration
,
buffer_duration
,
reinterpret_cast
<
WAVEFORMATEX
*>
(
&
device_format
),
nullptr
);
}
}
else
{
result
=
client
->
Initialize
(
AUDCLNT_SHAREMODE_SHARED
,
AUDCLNT_STREAMFLAGS_NOPERSIST
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
buffer_duration
,
0
,
reinterpret_cast
<
WAVEFORMATEX
*>
(
&
device_format
),
nullptr
);
}
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to initialize audio client"
);
}
ComPtr
<
IAudioRenderClient
>
render_client
;
result
=
client
->
GetService
(
IID_PPV_ARGS
(
render_client
.
Address
()));
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get new render client"
);
}
result
=
client
->
SetEventHandle
(
event
->
handle
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to set event handler"
);
}
UINT32
buffer_size_in_frames
;
result
=
client
->
GetBufferSize
(
&
buffer_size_in_frames
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get audio client buffer size"
);
}
watermark
=
buffer_size_in_frames
*
3
*
FrameSize
();
spsc_buffer
=
std
::
make_unique
<
boost
::
lockfree
::
spsc_queue
<
BYTE
>>
(
buffer_size_in_frames
*
4
*
FrameSize
());
thread
=
std
::
make_unique
<
WasapiOutputThread
>
(
event
,
client
,
std
::
move
(
render_client
),
FrameSize
(),
buffer_size_in_frames
,
is_exclusive
,
*
spsc_buffer
);
thread
->
Start
();
}
void
WasapiOutput
::
Close
()
noexcept
{
assert
(
client
&&
thread
);
Pause
();
thread
->
Finish
();
thread
->
Join
();
thread
.
reset
();
spsc_buffer
.
reset
();
client
.
reset
();
}
std
::
chrono
::
steady_clock
::
duration
WasapiOutput
::
Delay
()
const
noexcept
{
if
(
!
client
||
!
is_started
)
{
return
std
::
chrono
::
steady_clock
::
duration
::
zero
();
}
const
size_t
data_size
=
spsc_buffer
->
read_available
();
const
size_t
delay_size
=
std
::
max
(
data_size
,
watermark
)
-
watermark
;
using
s
=
std
::
chrono
::
seconds
;
using
duration
=
std
::
chrono
::
steady_clock
::
duration
;
auto
result
=
duration
(
s
(
delay_size
))
/
device_format
.
Format
.
nAvgBytesPerSec
;
return
result
;
}
size_t
WasapiOutput
::
Play
(
const
void
*
chunk
,
size_t
size
)
{
if
(
!
client
||
!
thread
)
{
return
0
;
}
do
{
const
size_t
consumed_size
=
spsc_buffer
->
push
(
static_cast
<
const
BYTE
*>
(
chunk
),
size
);
if
(
consumed_size
==
0
)
{
assert
(
is_started
);
thread
->
WaitWrite
();
continue
;
}
if
(
!
is_started
)
{
is_started
=
true
;
thread
->
Play
();
HRESULT
result
;
result
=
client
->
Start
();
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Failed to start client"
);
}
}
thread
->
CheckException
();
return
consumed_size
;
}
while
(
true
);
}
void
WasapiOutput
::
Drain
()
{
spsc_buffer
->
consume_all
([](
auto
&&
)
{});
thread
->
CheckException
();
}
bool
WasapiOutput
::
Pause
()
{
if
(
!
client
||
!
thread
)
{
return
false
;
}
if
(
!
is_started
)
{
return
true
;
}
HRESULT
result
;
result
=
client
->
Stop
();
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Failed to stop client"
);
}
is_started
=
false
;
thread
->
Pause
();
thread
->
CheckException
();
return
true
;
}
void
WasapiOutput
::
FindExclusiveFormatSupported
(
AudioFormat
&
audio_format
)
{
SetFormat
(
device_format
,
audio_format
);
do
{
HRESULT
result
;
result
=
client
->
IsFormatSupported
(
AUDCLNT_SHAREMODE_EXCLUSIVE
,
reinterpret_cast
<
WAVEFORMATEX
*>
(
&
device_format
),
nullptr
);
switch
(
result
)
{
case
S_OK
:
return
;
case
AUDCLNT_E_UNSUPPORTED_FORMAT
:
break
;
default
:
throw
FormatHResultError
(
result
,
"IsFormatSupported failed"
);
}
// Trying PCM fallback.
if
(
audio_format
.
format
==
SampleFormat
::
FLOAT
)
{
audio_format
.
format
=
SampleFormat
::
S32
;
continue
;
}
// Trying sample rate fallback.
if
(
audio_format
.
sample_rate
>
96000
)
{
audio_format
.
sample_rate
=
96000
;
continue
;
}
if
(
audio_format
.
sample_rate
>
88200
)
{
audio_format
.
sample_rate
=
88200
;
continue
;
}
if
(
audio_format
.
sample_rate
>
64000
)
{
audio_format
.
sample_rate
=
64000
;
continue
;
}
if
(
audio_format
.
sample_rate
>
48000
)
{
audio_format
.
sample_rate
=
48000
;
continue
;
}
// Trying 2 channels fallback.
if
(
audio_format
.
channels
>
2
)
{
audio_format
.
channels
=
2
;
continue
;
}
// Trying S16 fallback.
if
(
audio_format
.
format
==
SampleFormat
::
S32
)
{
audio_format
.
format
=
SampleFormat
::
S16
;
continue
;
}
if
(
audio_format
.
sample_rate
>
41100
)
{
audio_format
.
sample_rate
=
41100
;
continue
;
}
throw
FormatHResultError
(
result
,
"Format is not supported"
);
}
while
(
true
);
}
void
WasapiOutput
::
FindSharedFormatSupported
(
AudioFormat
&
audio_format
)
{
HRESULT
result
;
ComHeapPtr
<
WAVEFORMATEX
>
mixer_format
;
// In shared mode, different sample rate is always unsupported.
result
=
client
->
GetMixFormat
(
mixer_format
.
Address
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"GetMixFormat failed"
);
}
audio_format
.
sample_rate
=
device_format
.
Format
.
nSamplesPerSec
;
SetFormat
(
device_format
,
audio_format
);
ComHeapPtr
<
WAVEFORMATEXTENSIBLE
>
closest_format
;
result
=
client
->
IsFormatSupported
(
AUDCLNT_SHAREMODE_SHARED
,
reinterpret_cast
<
WAVEFORMATEX
*>
(
&
device_format
),
closest_format
.
AddressCast
<
WAVEFORMATEX
>
());
if
(
FAILED
(
result
)
&&
result
!=
AUDCLNT_E_UNSUPPORTED_FORMAT
)
{
throw
FormatHResultError
(
result
,
"IsFormatSupported failed"
);
}
switch
(
result
)
{
case
S_OK
:
break
;
case
AUDCLNT_E_UNSUPPORTED_FORMAT
:
default
:
// Trying channels fallback.
audio_format
.
channels
=
mixer_format
->
nChannels
;
SetFormat
(
device_format
,
audio_format
);
result
=
client
->
IsFormatSupported
(
AUDCLNT_SHAREMODE_SHARED
,
reinterpret_cast
<
WAVEFORMATEX
*>
(
&
device_format
),
closest_format
.
AddressCast
<
WAVEFORMATEX
>
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Format is not supported"
);
}
break
;
case
S_FALSE
:
if
(
closest_format
->
Format
.
wFormatTag
==
WAVE_FORMAT_EXTENSIBLE
)
{
device_format
=
*
closest_format
;
}
else
{
device_format
.
Samples
.
wValidBitsPerSample
=
device_format
.
Format
.
wBitsPerSample
;
device_format
.
Format
=
closest_format
->
Format
;
switch
(
std
::
exchange
(
device_format
.
Format
.
wFormatTag
,
WAVE_FORMAT_EXTENSIBLE
))
{
case
WAVE_FORMAT_PCM
:
device_format
.
SubFormat
=
KSDATAFORMAT_SUBTYPE_PCM
;
break
;
case
WAVE_FORMAT_IEEE_FLOAT
:
device_format
.
SubFormat
=
KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
break
;
default
:
gcc_unreachable
();
}
}
break
;
}
// Copy closest match back to audio_format.
audio_format
.
channels
=
device_format
.
Format
.
nChannels
;
audio_format
.
sample_rate
=
device_format
.
Format
.
nSamplesPerSec
;
if
(
device_format
.
SubFormat
==
KSDATAFORMAT_SUBTYPE_PCM
)
{
switch
(
device_format
.
Format
.
wBitsPerSample
)
{
case
8
:
audio_format
.
format
=
SampleFormat
::
S8
;
break
;
case
16
:
audio_format
.
format
=
SampleFormat
::
S16
;
break
;
case
32
:
audio_format
.
format
=
SampleFormat
::
S32
;
break
;
}
}
else
if
(
device_format
.
SubFormat
==
KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
)
{
audio_format
.
format
=
SampleFormat
::
FLOAT
;
}
}
void
WasapiOutput
::
EnumerateDevices
()
{
if
(
!
device_desc
.
empty
())
{
return
;
}
HRESULT
result
;
ComPtr
<
IMMDeviceCollection
>
device_collection
;
result
=
enumerator
->
EnumAudioEndpoints
(
eRender
,
DEVICE_STATE_ACTIVE
,
device_collection
.
Address
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to enumerate devices"
);
}
UINT
count
;
result
=
device_collection
->
GetCount
(
&
count
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Collection->GetCount failed"
);
}
device_desc
.
reserve
(
count
);
for
(
UINT
i
=
0
;
i
<
count
;
++
i
)
{
ComPtr
<
IMMDevice
>
enumerated_device
;
result
=
device_collection
->
Item
(
i
,
enumerated_device
.
Address
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Collection->Item failed"
);
}
ComPtr
<
IPropertyStore
>
property_store
;
result
=
enumerated_device
->
OpenPropertyStore
(
STGM_READ
,
property_store
.
Address
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Device->OpenPropertyStore failed"
);
}
PROPVARIANT
var_name
;
PropVariantInit
(
&
var_name
);
AtScopeExit
(
&
)
{
PropVariantClear
(
&
var_name
);
};
result
=
property_store
->
GetValue
(
PKEY_Device_FriendlyName
,
&
var_name
);
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"PropertyStore->GetValue failed"
);
}
device_desc
.
emplace_back
(
i
,
WideCharToMultiByte
(
CP_UTF8
,
std
::
wstring_view
(
var_name
.
pwszVal
)));
}
}
void
WasapiOutput
::
GetDevice
(
unsigned
int
index
)
{
HRESULT
result
;
ComPtr
<
IMMDeviceCollection
>
device_collection
;
result
=
enumerator
->
EnumAudioEndpoints
(
eRender
,
DEVICE_STATE_ACTIVE
,
device_collection
.
Address
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to enumerate devices"
);
}
result
=
device_collection
->
Item
(
index
,
device
.
Address
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Collection->Item failed"
);
}
}
unsigned
int
WasapiOutput
::
SearchDevice
(
std
::
string_view
name
)
{
if
(
!
SafeTry
([
this
]()
{
EnumerateDevices
();
}))
{
return
kErrorId
;
}
auto
iter
=
std
::
find_if
(
device_desc
.
cbegin
(),
device_desc
.
cend
(),
[
&
name
](
const
auto
&
desc
)
{
return
desc
.
second
==
name
;
});
if
(
iter
==
device_desc
.
cend
())
{
FormatError
(
wasapi_output_domain
,
"Device %.*s not founded."
,
int
(
name
.
size
()),
name
.
data
());
return
kErrorId
;
}
FormatInfo
(
wasapi_output_domain
,
"Select device
\"
%u
\"
\"
%s
\"
"
,
iter
->
first
,
iter
->
second
.
c_str
());
return
iter
->
first
;
}
void
WasapiOutput
::
GetDefaultDevice
()
{
HRESULT
result
;
result
=
enumerator
->
GetDefaultAudioEndpoint
(
eRender
,
eMultimedia
,
device
.
Address
());
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to get default device for multimedia"
);
}
}
static
bool
wasapi_output_test_default_device
()
{
return
true
;
}
const
struct
AudioOutputPlugin
wasapi_output_plugin
=
{
"wasapi"
,
wasapi_output_test_default_device
,
WasapiOutput
::
Create
,
&
wasapi_mixer_plugin
,
};
src/output/plugins/WasapiOutputPlugin.hxx
0 → 100644
View file @
93d87854
/*
* Copyright 2020 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.
*/
#ifndef MPD_WASAPI_OUTPUT_PLUGIN_HXX
#define MPD_WASAPI_OUTPUT_PLUGIN_HXX
#include "output/Features.h"
#include "../OutputAPI.hxx"
#include "util/Compiler.h"
#include "win32/ComPtr.hxx"
#include <audioclient.h>
#include <mmdeviceapi.h>
extern
const
struct
AudioOutputPlugin
wasapi_output_plugin
;
class
WasapiOutput
;
gcc_pure
WasapiOutput
&
wasapi_output_downcast
(
AudioOutput
&
output
)
noexcept
;
gcc_pure
bool
wasapi_is_exclusive
(
WasapiOutput
&
output
)
noexcept
;
gcc_pure
IMMDevice
*
wasapi_output_get_device
(
WasapiOutput
&
output
)
noexcept
;
gcc_pure
IAudioClient
*
wasapi_output_get_client
(
WasapiOutput
&
output
)
noexcept
;
#endif
src/output/plugins/meson.build
View file @
93d87854
...
...
@@ -39,7 +39,7 @@ if get_option('httpd')
'httpd/HttpdClient.cxx',
'httpd/HttpdOutputPlugin.cxx',
]
output_plugins_deps += [ event_dep, net_dep ]
output_plugins_deps += [ event_dep, net_dep
, boost_dep
]
need_encoder = true
endif
...
...
@@ -143,6 +143,19 @@ else
winmm_dep = dependency('', required: false)
endif
output_features.set('ENABLE_WASAPI_OUTPUT', is_windows)
if is_windows
output_plugins_sources += [
'WasapiOutputPlugin.cxx',
]
wasapi_dep = [
c_compiler.find_library('ksuser', required: true),
c_compiler.find_library('ole32', required: true),
]
else
wasapi_dep = dependency('', required: false)
endif
output_plugins = static_library(
'output_plugins',
output_plugins_sources,
...
...
@@ -158,6 +171,8 @@ output_plugins = static_library(
openal_dep,
sles_dep,
winmm_dep,
wasapi_dep,
boost_dep,
],
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment