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
25354b9d
Commit
25354b9d
authored
Mar 10, 2021
by
Max Kellermann
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'v0.22.x'
parents
ee720064
25b01940
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
558 additions
and
345 deletions
+558
-345
FileDescriptor.cxx
src/io/FileDescriptor.cxx
+9
-1
FileDescriptor.hxx
src/io/FileDescriptor.hxx
+3
-0
WasapiMixerPlugin.cxx
src/mixer/plugins/WasapiMixerPlugin.cxx
+16
-8
AudioClient.hxx
src/output/plugins/wasapi/AudioClient.hxx
+9
-9
Device.hxx
src/output/plugins/wasapi/Device.hxx
+9
-9
ForMixer.hxx
src/output/plugins/wasapi/ForMixer.hxx
+7
-0
WasapiOutputPlugin.cxx
src/output/plugins/wasapi/WasapiOutputPlugin.cxx
+390
-211
Com.hxx
src/win32/Com.hxx
+2
-10
ComPtr.hxx
src/win32/ComPtr.hxx
+1
-1
ComWorker.cxx
src/win32/ComWorker.cxx
+4
-6
ComWorker.hxx
src/win32/ComWorker.hxx
+30
-52
HResult.cxx
src/win32/HResult.cxx
+12
-1
HResult.hxx
src/win32/HResult.hxx
+9
-0
run_convert.cxx
test/run_convert.cxx
+27
-20
run_input.cxx
test/run_input.cxx
+2
-0
run_output.cxx
test/run_output.cxx
+28
-17
No files found.
src/io/FileDescriptor.cxx
View file @
25354b9d
...
...
@@ -172,7 +172,15 @@ FileDescriptor::CreatePipe(FileDescriptor &r, FileDescriptor &w) noexcept
#endif
}
#ifndef _WIN32
#ifdef _WIN32
void
FileDescriptor
::
SetBinaryMode
()
noexcept
{
_setmode
(
fd
,
_O_BINARY
);
}
#else // !_WIN32
bool
FileDescriptor
::
CreatePipeNonBlock
(
FileDescriptor
&
r
,
...
...
src/io/FileDescriptor.hxx
View file @
25354b9d
...
...
@@ -147,10 +147,13 @@ public:
#ifdef _WIN32
void
EnableCloseOnExec
()
noexcept
{}
void
DisableCloseOnExec
()
noexcept
{}
void
SetBinaryMode
()
noexcept
;
#else
static
bool
CreatePipeNonBlock
(
FileDescriptor
&
r
,
FileDescriptor
&
w
)
noexcept
;
void
SetBinaryMode
()
noexcept
{}
/**
* Enable non-blocking mode on this file descriptor.
*/
...
...
src/mixer/plugins/WasapiMixerPlugin.cxx
View file @
25354b9d
...
...
@@ -44,7 +44,11 @@ public:
void
Close
()
noexcept
override
{}
int
GetVolume
()
override
{
auto
future
=
COMWorker
::
Async
([
&
]()
->
int
{
auto
com_worker
=
wasapi_output_get_com_worker
(
output
);
if
(
!
com_worker
)
return
-
1
;
auto
future
=
com_worker
->
Async
([
&
]()
->
int
{
HRESULT
result
;
float
volume_level
;
...
...
@@ -55,9 +59,9 @@ public:
result
=
endpoint_volume
->
GetMasterVolumeLevelScalar
(
&
volume_level
);
if
(
FAILED
(
result
))
{
throw
Format
HResultError
(
result
,
"Unable to get master "
"volume level"
);
throw
Make
HResultError
(
result
,
"Unable to get master "
"volume level"
);
}
}
else
{
auto
session_volume
=
...
...
@@ -65,7 +69,7 @@ public:
result
=
session_volume
->
GetMasterVolume
(
&
volume_level
);
if
(
FAILED
(
result
))
{
throw
Format
HResultError
(
throw
Make
HResultError
(
result
,
"Unable to get master volume"
);
}
}
...
...
@@ -76,7 +80,11 @@ public:
}
void
SetVolume
(
unsigned
volume
)
override
{
COMWorker
::
Async
([
&
]()
{
auto
com_worker
=
wasapi_output_get_com_worker
(
output
);
if
(
!
com_worker
)
throw
std
::
runtime_error
(
"Cannot set WASAPI volume"
);
com_worker
->
Async
([
&
]()
{
HRESULT
result
;
const
float
volume_level
=
volume
/
100.0
f
;
...
...
@@ -87,7 +95,7 @@ public:
result
=
endpoint_volume
->
SetMasterVolumeLevelScalar
(
volume_level
,
nullptr
);
if
(
FAILED
(
result
))
{
throw
Format
HResultError
(
throw
Make
HResultError
(
result
,
"Unable to set master volume level"
);
}
...
...
@@ -98,7 +106,7 @@ public:
result
=
session_volume
->
SetMasterVolume
(
volume_level
,
nullptr
);
if
(
FAILED
(
result
))
{
throw
Format
HResultError
(
throw
Make
HResultError
(
result
,
"Unable to set master volume"
);
}
}
...
...
src/output/plugins/wasapi/AudioClient.hxx
View file @
25354b9d
...
...
@@ -33,8 +33,8 @@ GetBufferSizeInFrames(IAudioClient &client)
HRESULT
result
=
client
.
GetBufferSize
(
&
buffer_size_in_frames
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Unable to get audio client buffer size"
);
throw
Make
HResultError
(
result
,
"Unable to get audio client buffer size"
);
return
buffer_size_in_frames
;
}
...
...
@@ -46,8 +46,8 @@ GetCurrentPaddingFrames(IAudioClient &client)
HRESULT
result
=
client
.
GetCurrentPadding
(
&
padding_frames
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Failed to get current padding"
);
throw
Make
HResultError
(
result
,
"Failed to get current padding"
);
return
padding_frames
;
}
...
...
@@ -59,7 +59,7 @@ GetMixFormat(IAudioClient &client)
HRESULT
result
=
client
.
GetMixFormat
(
&
f
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"GetMixFormat failed"
);
throw
Make
HResultError
(
result
,
"GetMixFormat failed"
);
return
ComHeapPtr
{
f
};
}
...
...
@@ -69,7 +69,7 @@ Start(IAudioClient &client)
{
HRESULT
result
=
client
.
Start
();
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Failed to start client"
);
throw
Make
HResultError
(
result
,
"Failed to start client"
);
}
inline
void
...
...
@@ -77,7 +77,7 @@ Stop(IAudioClient &client)
{
HRESULT
result
=
client
.
Stop
();
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Failed to stop client"
);
throw
Make
HResultError
(
result
,
"Failed to stop client"
);
}
inline
void
...
...
@@ -85,7 +85,7 @@ SetEventHandle(IAudioClient &client, HANDLE h)
{
HRESULT
result
=
client
.
SetEventHandle
(
h
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Unable to set event handle"
);
throw
Make
HResultError
(
result
,
"Unable to set event handle"
);
}
template
<
typename
T
>
...
...
@@ -95,7 +95,7 @@ GetService(IAudioClient &client)
T
*
p
=
nullptr
;
HRESULT
result
=
client
.
GetService
(
IID_PPV_ARGS
(
&
p
));
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Unable to get service"
);
throw
Make
HResultError
(
result
,
"Unable to get service"
);
return
ComPtr
{
p
};
}
...
...
src/output/plugins/wasapi/Device.hxx
View file @
25354b9d
...
...
@@ -33,8 +33,8 @@ GetDefaultAudioEndpoint(IMMDeviceEnumerator &e)
HRESULT
result
=
e
.
GetDefaultAudioEndpoint
(
eRender
,
eMultimedia
,
&
device
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Unable to get default device for multimedia"
);
throw
Make
HResultError
(
result
,
"Unable to get default device for multimedia"
);
return
ComPtr
{
device
};
}
...
...
@@ -47,7 +47,7 @@ EnumAudioEndpoints(IMMDeviceEnumerator &e)
HRESULT
result
=
e
.
EnumAudioEndpoints
(
eRender
,
DEVICE_STATE_ACTIVE
,
&
dc
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Unable to enumerate devices"
);
throw
Make
HResultError
(
result
,
"Unable to enumerate devices"
);
return
ComPtr
{
dc
};
}
...
...
@@ -59,7 +59,7 @@ GetCount(IMMDeviceCollection &dc)
HRESULT
result
=
dc
.
GetCount
(
&
count
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Collection->GetCount failed"
);
throw
Make
HResultError
(
result
,
"Collection->GetCount failed"
);
return
count
;
}
...
...
@@ -71,7 +71,7 @@ Item(IMMDeviceCollection &dc, UINT i)
auto
result
=
dc
.
Item
(
i
,
&
device
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Collection->Item failed"
);
throw
Make
HResultError
(
result
,
"Collection->Item failed"
);
return
ComPtr
{
device
};
}
...
...
@@ -83,7 +83,7 @@ GetState(IMMDevice &device)
HRESULT
result
=
device
.
GetState
(
&
state
);;
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Unable to get device status"
);
throw
Make
HResultError
(
result
,
"Unable to get device status"
);
return
state
;
}
...
...
@@ -96,7 +96,7 @@ Activate(IMMDevice &device)
HRESULT
result
=
device
.
Activate
(
__uuidof
(
T
),
CLSCTX_ALL
,
nullptr
,
(
void
**
)
&
p
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Unable to activate device"
);
throw
Make
HResultError
(
result
,
"Unable to activate device"
);
return
ComPtr
{
p
};
}
...
...
@@ -108,8 +108,8 @@ OpenPropertyStore(IMMDevice &device)
HRESULT
result
=
device
.
OpenPropertyStore
(
STGM_READ
,
&
property_store
);
if
(
FAILED
(
result
))
throw
Format
HResultError
(
result
,
"Device->OpenPropertyStore failed"
);
throw
Make
HResultError
(
result
,
"Device->OpenPropertyStore failed"
);
return
ComPtr
{
property_store
};
}
...
...
src/output/plugins/wasapi/ForMixer.hxx
View file @
25354b9d
...
...
@@ -20,10 +20,13 @@
#ifndef MPD_WASAPI_OUTPUT_FOR_MIXER_HXX
#define MPD_WASAPI_OUTPUT_FOR_MIXER_HXX
#include <memory>
struct
IMMDevice
;
struct
IAudioClient
;
class
AudioOutput
;
class
WasapiOutput
;
class
COMWorker
;
[[
gnu
::
pure
]]
WasapiOutput
&
...
...
@@ -34,6 +37,10 @@ bool
wasapi_is_exclusive
(
WasapiOutput
&
output
)
noexcept
;
[[
gnu
::
pure
]]
std
::
shared_ptr
<
COMWorker
>
wasapi_output_get_com_worker
(
WasapiOutput
&
output
)
noexcept
;
[[
gnu
::
pure
]]
IMMDevice
*
wasapi_output_get_device
(
WasapiOutput
&
output
)
noexcept
;
...
...
src/output/plugins/wasapi/WasapiOutputPlugin.cxx
View file @
25354b9d
...
...
@@ -61,7 +61,9 @@
namespace
{
static
constexpr
Domain
wasapi_output_domain
(
"wasapi_output"
);
gcc_const
constexpr
uint32_t
GetChannelMask
(
const
uint8_t
channels
)
noexcept
{
constexpr
uint32_t
GetChannelMask
(
const
uint8_t
channels
)
noexcept
{
switch
(
channels
)
{
case
1
:
return
KSAUDIO_SPEAKER_MONO
;
...
...
@@ -86,18 +88,9 @@ gcc_const constexpr uint32_t GetChannelMask(const uint8_t channels) noexcept {
}
template
<
typename
Functor
>
inline
bool
SafeTry
(
Functor
&&
functor
)
{
try
{
functor
();
return
true
;
}
catch
(...)
{
FormatError
(
std
::
current_exception
(),
"%s"
);
return
false
;
}
}
template
<
typename
Functor
>
inline
bool
SafeSilenceTry
(
Functor
&&
functor
)
{
inline
bool
SafeSilenceTry
(
Functor
&&
functor
)
noexcept
{
try
{
functor
();
return
true
;
...
...
@@ -106,7 +99,9 @@ inline bool SafeSilenceTry(Functor &&functor) {
}
}
std
::
vector
<
WAVEFORMATEXTENSIBLE
>
GetFormats
(
const
AudioFormat
&
audio_format
)
noexcept
{
std
::
vector
<
WAVEFORMATEXTENSIBLE
>
GetFormats
(
const
AudioFormat
&
audio_format
)
noexcept
{
#ifdef ENABLE_DSD
if
(
audio_format
.
format
==
SampleFormat
::
DSD
)
{
AudioFormat
dop_format
=
audio_format
;
...
...
@@ -152,57 +147,154 @@ std::vector<WAVEFORMATEXTENSIBLE> GetFormats(const AudioFormat &audio_format) no
}
#ifdef ENABLE_DSD
void
SetDSDFallback
(
AudioFormat
&
audio_format
)
noexcept
{
void
SetDSDFallback
(
AudioFormat
&
audio_format
)
noexcept
{
audio_format
.
format
=
SampleFormat
::
FLOAT
;
audio_format
.
sample_rate
=
384000
;
}
#endif
inline
constexpr
const
unsigned
int
kErrorId
=
-
1
;
}
// namespace
class
WasapiOutputThread
:
public
Thread
{
public
:
enum
class
Status
:
uint32_t
{
FINISH
,
PLAY
,
PAUSE
};
WasapiOutputThread
(
IAudioClient
*
_client
,
ComPtr
<
IAudioRenderClient
>
&&
_render_client
,
const
UINT32
_frame_size
,
const
UINT32
_buffer_size_in_frames
,
bool
_is_exclusive
)
:
Thread
(
BIND_THIS_METHOD
(
Work
)),
client
(
_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
(
_buffer_size_in_frames
*
4
*
_frame_size
)
{}
void
Finish
()
noexcept
{
return
SetStatus
(
Status
::
FINISH
);
}
void
Play
()
noexcept
{
return
SetStatus
(
Status
::
PLAY
);
}
void
Pause
()
noexcept
{
return
SetStatus
(
Status
::
PAUSE
);
}
void
WaitDataPoped
()
noexcept
{
data_poped
.
Wait
();
}
void
CheckException
()
{
if
(
error
.
occur
.
load
())
{
auto
err
=
std
::
exchange
(
error
.
ptr
,
nullptr
);
error
.
thrown
.
Set
();
std
::
rethrow_exception
(
err
);
}
}
private
:
friend
class
WasapiOutput
;
class
WasapiOutputThread
{
Thread
thread
{
BIND_THIS_METHOD
(
Work
)};
WinEvent
event
;
WinEvent
data_poped
;
IAudioClient
*
client
;
IAudioClient
&
client
;
ComPtr
<
IAudioRenderClient
>
render_client
;
const
UINT32
frame_size
;
const
UINT32
buffer_size_in_frames
;
bool
is_exclusive
;
const
bool
is_exclusive
;
/**
* This flag is only used by the calling thread
* (i.e. #OutputThread), and specifies whether the
* WasapiOutputThread has been told to play via Play(). This
* variable is somewhat redundant because we already have
* "state", but using this variable saves some overhead for
* atomic operations.
*/
bool
playing
=
false
;
bool
started
=
false
;
std
::
atomic_bool
cancel
=
false
;
std
::
atomic_bool
empty
=
true
;
enum
class
Status
:
uint32_t
{
FINISH
,
PLAY
,
PAUSE
};
alignas
(
BOOST_LOCKFREE_CACHELINE_BYTES
)
std
::
atomic
<
Status
>
status
=
Status
::
PAUSE
;
alignas
(
BOOST_LOCKFREE_CACHELINE_BYTES
)
struct
{
std
::
atomic_bool
occur
=
false
;
std
::
exception_ptr
ptr
=
nullptr
;
WinEvent
thrown
;
}
error
;
boost
::
lockfree
::
spsc_queue
<
BYTE
>
spsc_buffer
;
public
:
WasapiOutputThread
(
IAudioClient
&
_client
,
ComPtr
<
IAudioRenderClient
>
&&
_render_client
,
const
UINT32
_frame_size
,
const
UINT32
_buffer_size_in_frames
,
bool
_is_exclusive
)
:
client
(
_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
(
_buffer_size_in_frames
*
4
*
_frame_size
)
{
SetEventHandle
(
client
,
event
.
handle
());
thread
.
Start
();
}
void
Finish
()
noexcept
{
SetStatus
(
Status
::
FINISH
);
thread
.
Join
();
}
void
Play
()
noexcept
{
playing
=
true
;
SetStatus
(
Status
::
PLAY
);
}
void
Pause
()
noexcept
{
if
(
!
playing
)
return
;
playing
=
false
;
SetStatus
(
Status
::
PAUSE
);
}
std
::
size_t
Push
(
ConstBuffer
<
void
>
input
)
noexcept
{
empty
.
store
(
false
);
std
::
size_t
consumed
=
spsc_buffer
.
push
(
static_cast
<
const
BYTE
*>
(
input
.
data
),
input
.
size
);
if
(
!
playing
)
{
playing
=
true
;
Play
();
}
return
consumed
;
}
/**
* Check if the buffer is empty, and if not, wait a bit.
*
* Throws on error.
*
* @return true if the buffer is now empty
*/
bool
Drain
()
{
if
(
empty
)
return
true
;
CheckException
();
Wait
();
CheckException
();
return
empty
;
}
/**
* Instruct the thread to discard the buffer (and wait for
* completion). This needs to be done inside this thread,
* because only the consumer thread is allowed to do that.
*/
void
Cancel
()
noexcept
{
cancel
.
store
(
true
);
event
.
Set
();
while
(
cancel
.
load
()
&&
!
error
.
occur
.
load
())
Wait
();
/* not rethrowing the exception here via
CheckException() because this method must be
"noexcept"; the next WasapiOutput::Play() call will
throw */
}
/**
* Wait for the thread to finish some work (e.g. until some
* buffer space becomes available).
*/
void
Wait
()
noexcept
{
data_poped
.
Wait
();
}
void
InterruptWaiter
()
noexcept
{
data_poped
.
Set
();
}
void
CheckException
()
{
if
(
error
.
occur
.
load
())
{
std
::
rethrow_exception
(
error
.
ptr
);
}
}
private
:
void
SetStatus
(
Status
s
)
noexcept
{
status
.
store
(
s
);
event
.
Set
();
...
...
@@ -211,30 +303,61 @@ private:
};
class
WasapiOutput
final
:
public
AudioOutput
{
const
bool
is_exclusive
;
const
bool
enumerate_devices
;
#ifdef ENABLE_DSD
const
bool
dop_setting
;
#endif
/**
* Only valid if the output is open.
*/
bool
paused
;
std
::
atomic_flag
not_interrupted
=
true
;
const
std
::
string
device_config
;
std
::
shared_ptr
<
COMWorker
>
com_worker
;
ComPtr
<
IMMDevice
>
device
;
ComPtr
<
IAudioClient
>
client
;
WAVEFORMATEXTENSIBLE
device_format
;
std
::
optional
<
WasapiOutputThread
>
thread
;
std
::
size_t
watermark
;
std
::
optional
<
PcmExport
>
pcm_export
;
public
:
static
AudioOutput
*
Create
(
EventLoop
&
,
const
ConfigBlock
&
block
);
WasapiOutput
(
const
ConfigBlock
&
block
);
auto
GetComWorker
()
noexcept
{
// TODO: protect access to the shard_ptr
return
com_worker
;
}
void
Enable
()
override
{
COMWorker
::
Aquire
();
com_worker
=
std
::
make_shared
<
COMWorker
>
();
try
{
COMWorker
::
Async
([
&
]()
{
Open
Device
();
}).
get
();
com_worker
->
Async
([
&
]()
{
Choose
Device
();
}).
get
();
}
catch
(...)
{
COMWorker
::
Release
();
com_worker
.
reset
();
throw
;
}
}
void
Disable
()
noexcept
override
{
COMWorker
::
Async
([
&
]()
{
DoDisable
();
}).
get
();
COMWorker
::
Release
();
com_worker
->
Async
([
&
]()
{
DoDisable
();
}).
get
();
com_worker
.
reset
();
}
void
Open
(
AudioFormat
&
audio_format
)
override
{
COMWorker
::
Async
([
&
]()
{
DoOpen
(
audio_format
);
}).
get
();
com_worker
->
Async
([
&
]()
{
DoOpen
(
audio_format
);
}).
get
();
paused
=
false
;
}
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
;
void
Cancel
()
noexcept
override
;
bool
Pause
()
override
;
void
Interrupt
()
noexcept
override
;
...
...
@@ -245,22 +368,6 @@ public:
}
private
:
std
::
atomic_flag
not_interrupted
=
true
;
bool
is_started
=
false
;
bool
is_exclusive
;
bool
enumerate_devices
;
#ifdef ENABLE_DSD
bool
dop_setting
;
#endif
std
::
string
device_config
;
ComPtr
<
IMMDeviceEnumerator
>
enumerator
;
ComPtr
<
IMMDevice
>
device
;
ComPtr
<
IAudioClient
>
client
;
WAVEFORMATEXTENSIBLE
device_format
;
std
::
optional
<
WasapiOutputThread
>
thread
;
std
::
size_t
watermark
;
std
::
optional
<
PcmExport
>
pcm_export
;
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
;
...
...
@@ -268,126 +375,179 @@ private:
void
DoDisable
()
noexcept
;
void
DoOpen
(
AudioFormat
&
audio_format
);
void
Open
Device
();
void
Choose
Device
();
bool
TryFormatExclusive
(
const
AudioFormat
&
audio_format
);
void
FindExclusiveFormatSupported
(
AudioFormat
&
audio_format
);
void
FindSharedFormatSupported
(
AudioFormat
&
audio_format
);
void
EnumerateDevices
();
ComPtr
<
IMMDevice
>
GetDevice
(
unsigned
int
index
);
ComPtr
<
IMMDevice
>
SearchDevice
(
std
::
string_view
name
);
static
void
EnumerateDevices
(
IMMDeviceEnumerator
&
enumerator
);
static
ComPtr
<
IMMDevice
>
GetDevice
(
IMMDeviceEnumerator
&
enumerator
,
unsigned
index
);
static
ComPtr
<
IMMDevice
>
SearchDevice
(
IMMDeviceEnumerator
&
enumerator
,
std
::
string_view
name
);
};
WasapiOutput
&
wasapi_output_downcast
(
AudioOutput
&
output
)
noexcept
{
WasapiOutput
&
wasapi_output_downcast
(
AudioOutput
&
output
)
noexcept
{
return
static_cast
<
WasapiOutput
&>
(
output
);
}
bool
wasapi_is_exclusive
(
WasapiOutput
&
output
)
noexcept
{
return
output
.
is_exclusive
;
}
bool
wasapi_is_exclusive
(
WasapiOutput
&
output
)
noexcept
{
return
output
.
is_exclusive
;
}
IMMDevice
*
wasapi_output_get_device
(
WasapiOutput
&
output
)
noexcept
{
std
::
shared_ptr
<
COMWorker
>
wasapi_output_get_com_worker
(
WasapiOutput
&
output
)
noexcept
{
return
output
.
GetComWorker
();
}
IMMDevice
*
wasapi_output_get_device
(
WasapiOutput
&
output
)
noexcept
{
return
output
.
device
.
get
();
}
IAudioClient
*
wasapi_output_get_client
(
WasapiOutput
&
output
)
noexcept
{
IAudioClient
*
wasapi_output_get_client
(
WasapiOutput
&
output
)
noexcept
{
return
output
.
client
.
get
();
}
void
WasapiOutputThread
::
Work
()
noexcept
{
inline
void
WasapiOutputThread
::
Work
()
noexcept
try
{
SetThreadName
(
"Wasapi Output Worker"
);
FormatDebug
(
wasapi_output_domain
,
"Working thread started"
);
COM
com
{
true
};
COM
com
;
AtScopeExit
(
this
)
{
if
(
started
)
{
try
{
Stop
(
client
);
}
catch
(...)
{
LogError
(
std
::
current_exception
());
}
}
};
while
(
true
)
{
try
{
event
.
Wait
();
event
.
Wait
();
Status
current_state
=
status
.
load
();
if
(
current_state
==
Status
::
FINISH
)
{
FormatDebug
(
wasapi_output_domain
,
"Working thread stopped"
);
return
;
}
if
(
cancel
.
load
())
{
spsc_buffer
.
consume_all
([](
auto
&&
)
{});
cancel
.
store
(
false
);
empty
.
store
(
true
);
InterruptWaiter
()
;
}
UINT32
write_in_frames
=
buffer_size_in_frames
;
if
(
!
is_exclusive
)
{
UINT32
data_in_frames
=
GetCurrentPaddingFrames
(
*
client
);
Status
current_state
=
status
.
load
();
switch
(
current_state
)
{
case
Status
:
:
FINISH
:
FormatDebug
(
wasapi_output_domain
,
"Working thread stopped"
);
return
;
if
(
data_in_frames
>=
buffer_size_in_frames
)
{
continue
;
}
write_in_frames
-=
data_in_frames
;
}
case
Status
:
:
PAUSE
:
if
(
!
started
)
/* don't bother starting the
IAudioClient if we're paused */
continue
;
BYTE
*
data
;
DWORD
mode
=
0
;
/* stop the IAudioClient while paused; it will
be restarted as soon as we're asked to
resume playback */
Stop
(
client
);
started
=
false
;
continue
;
if
(
HRESULT
result
=
render_client
->
GetBuffer
(
write_in_frames
,
&
data
);
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Failed to get buffer"
);
}
case
Status
:
:
PLAY
:
break
;
}
AtScopeExit
(
&
)
{
render_client
->
ReleaseBuffer
(
write_in_frames
,
mode
);
};
if
(
current_state
==
Status
::
PLAY
)
{
const
UINT32
write_size
=
write_in_frames
*
frame_size
;
UINT32
new_data_size
=
0
;
new_data_size
=
spsc_buffer
.
pop
(
data
,
write_size
);
std
::
fill_n
(
data
+
new_data_size
,
write_size
-
new_data_size
,
0
);
data_poped
.
Set
();
}
else
{
mode
=
AUDCLNT_BUFFERFLAGS_SILENT
;
FormatDebug
(
wasapi_output_domain
,
"Working thread paused"
);
UINT32
write_in_frames
=
buffer_size_in_frames
;
if
(
!
is_exclusive
)
{
UINT32
data_in_frames
=
GetCurrentPaddingFrames
(
client
);
if
(
data_in_frames
>=
buffer_size_in_frames
)
{
continue
;
}
}
catch
(...)
{
error
.
ptr
=
std
::
current_exception
();
error
.
occur
.
store
(
true
);
error
.
thrown
.
Wait
();
write_in_frames
-=
data_in_frames
;
}
BYTE
*
data
;
DWORD
mode
=
0
;
if
(
HRESULT
result
=
render_client
->
GetBuffer
(
write_in_frames
,
&
data
);
FAILED
(
result
))
{
throw
MakeHResultError
(
result
,
"Failed to get buffer"
);
}
AtScopeExit
(
&
)
{
render_client
->
ReleaseBuffer
(
write_in_frames
,
mode
);
if
(
!
started
)
{
Start
(
client
);
started
=
true
;
}
};
const
UINT32
write_size
=
write_in_frames
*
frame_size
;
UINT32
new_data_size
=
0
;
new_data_size
=
spsc_buffer
.
pop
(
data
,
write_size
);
if
(
new_data_size
==
0
)
empty
.
store
(
true
);
std
::
fill_n
(
data
+
new_data_size
,
write_size
-
new_data_size
,
0
);
InterruptWaiter
();
}
}
catch
(...)
{
error
.
ptr
=
std
::
current_exception
();
error
.
occur
.
store
(
true
);
/* wake up the client thread which may be inside Wait() */
InterruptWaiter
();
}
AudioOutput
*
WasapiOutput
::
Create
(
EventLoop
&
,
const
ConfigBlock
&
block
)
{
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
)),
:
AudioOutput
(
FLAG_ENABLE_DISABLE
|
FLAG_PAUSE
),
is_exclusive
(
block
.
GetBlockValue
(
"exclusive"
,
false
)),
enumerate_devices
(
block
.
GetBlockValue
(
"enumerate"
,
false
)),
#ifdef ENABLE_DSD
dop_setting
(
block
.
GetBlockValue
(
"dop"
,
false
)),
dop_setting
(
block
.
GetBlockValue
(
"dop"
,
false
)),
#endif
device_config
(
block
.
GetBlockValue
(
"device"
,
""
))
{
device_config
(
block
.
GetBlockValue
(
"device"
,
""
))
{
}
/// run inside COMWorkerThread
void
WasapiOutput
::
DoDisable
()
noexcept
{
if
(
thread
)
{
try
{
thread
->
Finish
();
thread
->
Join
();
}
catch
(
std
::
exception
&
err
)
{
FormatError
(
wasapi_output_domain
,
"exception while disabling: %s"
,
err
.
what
());
}
thread
.
reset
();
client
.
reset
();
}
void
WasapiOutput
::
DoDisable
()
noexcept
{
assert
(
!
thread
);
device
.
reset
();
enumerator
.
reset
();
}
/// run inside COMWorkerThread
void
WasapiOutput
::
DoOpen
(
AudioFormat
&
audio_format
)
{
void
WasapiOutput
::
DoOpen
(
AudioFormat
&
audio_format
)
{
client
.
reset
();
if
(
GetState
(
*
device
)
!=
DEVICE_STATE_ACTIVE
)
{
device
.
reset
();
Open
Device
();
Choose
Device
();
}
client
=
Activate
<
IAudioClient
>
(
*
device
);
...
...
@@ -444,7 +604,7 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
if
(
HRESULT
result
=
client
->
GetDevicePeriod
(
&
default_device_period
,
&
min_device_period
);
FAILED
(
result
))
{
throw
Format
HResultError
(
result
,
"Unable to get device period"
);
throw
Make
HResultError
(
result
,
"Unable to get device period"
);
}
FormatDebug
(
wasapi_output_domain
,
"Default device period: %I64u ns, Minimum device period: "
...
...
@@ -492,8 +652,7 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
}
if
(
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to initialize audio client"
);
throw
MakeHResultError
(
result
,
"Unable to initialize audio client"
);
}
}
}
else
{
...
...
@@ -502,8 +661,8 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
buffer_duration
,
0
,
reinterpret_cast
<
WAVEFORMATEX
*>
(
&
device_format
),
nullptr
);
FAILED
(
result
))
{
throw
Format
HResultError
(
result
,
"Unable to initialize audio client"
);
throw
Make
HResultError
(
result
,
"Unable to initialize audio client"
);
}
}
...
...
@@ -512,56 +671,49 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
const
UINT32
buffer_size_in_frames
=
GetBufferSizeInFrames
(
*
client
);
watermark
=
buffer_size_in_frames
*
3
*
FrameSize
();
thread
.
emplace
(
client
.
get
()
,
std
::
move
(
render_client
),
FrameSize
(),
thread
.
emplace
(
*
client
,
std
::
move
(
render_client
),
FrameSize
(),
buffer_size_in_frames
,
is_exclusive
);
SetEventHandle
(
*
client
,
thread
->
event
.
handle
());
thread
->
Start
();
paused
=
false
;
}
void
WasapiOutput
::
Close
()
noexcept
{
void
WasapiOutput
::
Close
()
noexcept
{
assert
(
thread
);
try
{
COMWorker
::
Async
([
&
]()
{
Stop
(
*
client
);
}).
get
();
thread
->
CheckException
();
}
catch
(
std
::
exception
&
err
)
{
FormatError
(
wasapi_output_domain
,
"exception while stoping: %s"
,
err
.
what
()
);
}
catch
(
...
)
{
FormatError
(
std
::
current_exception
()
,
"exception while stopping"
);
}
is_started
=
false
;
thread
->
Finish
();
thread
->
Join
();
COMWorker
::
Async
([
&
]()
{
com_worker
->
Async
([
&
]()
{
thread
.
reset
();
client
.
reset
();
}).
get
();
pcm_export
.
reset
();
}
std
::
chrono
::
steady_clock
::
duration
WasapiOutput
::
Delay
()
const
noexcept
{
if
(
!
is_started
)
{
std
::
chrono
::
steady_clock
::
duration
WasapiOutput
::
Delay
()
const
noexcept
{
if
(
paused
)
{
// idle while paused
return
std
::
chrono
::
seconds
(
1
);
}
assert
(
thread
);
const
size_t
data_size
=
thread
->
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
;
return
std
::
chrono
::
steady_clock
::
duration
::
zero
();
}
size_t
WasapiOutput
::
Play
(
const
void
*
chunk
,
size_t
size
)
{
size_t
WasapiOutput
::
Play
(
const
void
*
chunk
,
size_t
size
)
{
assert
(
thread
);
paused
=
false
;
not_interrupted
.
test_and_set
();
ConstBuffer
<
void
>
input
(
chunk
,
size
);
...
...
@@ -572,25 +724,17 @@ size_t WasapiOutput::Play(const void *chunk, size_t size) {
return
size
;
do
{
const
size_t
consumed_size
=
thread
->
spsc_buffer
.
push
(
static_cast
<
const
BYTE
*>
(
input
.
data
),
input
.
size
);
const
size_t
consumed_size
=
thread
->
Push
({
chunk
,
size
});
if
(
consumed_size
==
0
)
{
assert
(
is_started
);
thread
->
WaitDataPoped
();
thread
->
Wait
(
);
thread
->
CheckException
();
if
(
!
not_interrupted
.
test_and_set
())
{
throw
AudioOutputInterrupted
{};
}
continue
;
}
if
(
!
is_started
)
{
is_started
=
true
;
thread
->
Play
();
COMWorker
::
Async
([
&
]()
{
Start
(
*
client
);
}).
wait
();
}
thread
->
CheckException
();
if
(
pcm_export
)
{
...
...
@@ -600,58 +744,82 @@ size_t WasapiOutput::Play(const void *chunk, size_t size) {
}
while
(
true
);
}
bool
WasapiOutput
::
Pause
()
{
if
(
is_started
)
{
thread
->
Pause
();
is_started
=
fals
e
;
}
bool
WasapiOutput
::
Pause
()
{
paused
=
tru
e
;
thread
->
Pause
();
thread
->
CheckException
();
return
true
;
}
void
WasapiOutput
::
Interrupt
()
noexcept
{
void
WasapiOutput
::
Interrupt
()
noexcept
{
if
(
thread
)
{
not_interrupted
.
clear
();
thread
->
data_poped
.
Set
();
thread
->
InterruptWaiter
();
}
}
void
WasapiOutput
::
Drain
()
{
void
WasapiOutput
::
Drain
()
{
assert
(
thread
);
thread
->
spsc_buffer
.
consume_all
([](
auto
&&
)
{});
thread
->
CheckException
();
not_interrupted
.
test_and_set
();
while
(
!
thread
->
Drain
())
{
if
(
!
not_interrupted
.
test_and_set
())
throw
AudioOutputInterrupted
{};
}
/* TODO: this needs to wait until the hardware has really
finished playing */
}
void
WasapiOutput
::
Cancel
()
noexcept
{
assert
(
thread
);
thread
->
Cancel
();
}
/// run inside COMWorkerThread
void
WasapiOutput
::
OpenDevice
()
{
void
WasapiOutput
::
ChooseDevice
()
{
ComPtr
<
IMMDeviceEnumerator
>
enumerator
;
enumerator
.
CoCreateInstance
(
__uuidof
(
MMDeviceEnumerator
),
nullptr
,
CLSCTX_INPROC_SERVER
);
if
(
enumerate_devices
)
{
try
{
EnumerateDevices
();
EnumerateDevices
(
*
enumerator
);
}
catch
(...)
{
LogError
(
std
::
current_exception
());
}
}
unsigned
int
id
=
kErrorId
;
if
(
!
device_config
.
empty
())
{
unsigned
int
id
;
if
(
!
SafeSilenceTry
([
this
,
&
id
]()
{
id
=
std
::
stoul
(
device_config
);
}))
{
device
=
SearchDevice
(
device_config
);
device
=
SearchDevice
(
*
enumerator
,
device_config
);
if
(
!
device
)
throw
FormatRuntimeError
(
"Device '%s' not found"
,
device_config
.
c_str
());
}
else
device
=
GetDevice
(
id
);
device
=
GetDevice
(
*
enumerator
,
id
);
}
else
{
device
=
GetDefaultAudioEndpoint
(
*
enumerator
);
}
}
/// run inside COMWorkerThread
bool
WasapiOutput
::
TryFormatExclusive
(
const
AudioFormat
&
audio_format
)
{
bool
WasapiOutput
::
TryFormatExclusive
(
const
AudioFormat
&
audio_format
)
{
for
(
auto
test_format
:
GetFormats
(
audio_format
))
{
HRESULT
result
=
client
->
IsFormatSupported
(
AUDCLNT_SHAREMODE_EXCLUSIVE
,
...
...
@@ -675,7 +843,9 @@ bool WasapiOutput::TryFormatExclusive(const AudioFormat &audio_format) {
}
/// run inside COMWorkerThread
void
WasapiOutput
::
FindExclusiveFormatSupported
(
AudioFormat
&
audio_format
)
{
void
WasapiOutput
::
FindExclusiveFormatSupported
(
AudioFormat
&
audio_format
)
{
for
(
uint8_t
channels
:
{
0
,
2
,
6
,
8
,
7
,
1
,
4
,
5
,
3
})
{
if
(
audio_format
.
channels
==
channels
)
{
continue
;
...
...
@@ -735,7 +905,9 @@ void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
}
/// run inside COMWorkerThread
void
WasapiOutput
::
FindSharedFormatSupported
(
AudioFormat
&
audio_format
)
{
void
WasapiOutput
::
FindSharedFormatSupported
(
AudioFormat
&
audio_format
)
{
HRESULT
result
;
// In shared mode, different sample rate is always unsupported.
...
...
@@ -760,7 +932,7 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
}
if
(
FAILED
(
result
)
&&
result
!=
AUDCLNT_E_UNSUPPORTED_FORMAT
)
{
throw
Format
HResultError
(
result
,
"IsFormatSupported failed"
);
throw
Make
HResultError
(
result
,
"IsFormatSupported failed"
);
}
switch
(
result
)
{
...
...
@@ -789,7 +961,7 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
result_string
.
c_str
());
}
if
(
FAILED
(
result
))
{
throw
Format
HResultError
(
result
,
"Format is not supported"
);
throw
Make
HResultError
(
result
,
"Format is not supported"
);
}
break
;
case
S_FALSE
:
...
...
@@ -838,8 +1010,10 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
}
/// run inside COMWorkerThread
void
WasapiOutput
::
EnumerateDevices
()
{
const
auto
device_collection
=
EnumAudioEndpoints
(
*
enumerator
);
void
WasapiOutput
::
EnumerateDevices
(
IMMDeviceEnumerator
&
enumerator
)
{
const
auto
device_collection
=
EnumAudioEndpoints
(
enumerator
);
const
UINT
count
=
GetCount
(
*
device_collection
);
for
(
UINT
i
=
0
;
i
<
count
;
++
i
)
{
...
...
@@ -860,17 +1034,18 @@ void WasapiOutput::EnumerateDevices() {
/// run inside COMWorkerThread
ComPtr
<
IMMDevice
>
WasapiOutput
::
GetDevice
(
unsigned
int
index
)
WasapiOutput
::
GetDevice
(
IMMDeviceEnumerator
&
enumerator
,
unsigned
index
)
{
const
auto
device_collection
=
EnumAudioEndpoints
(
*
enumerator
);
const
auto
device_collection
=
EnumAudioEndpoints
(
enumerator
);
return
Item
(
*
device_collection
,
index
);
}
/// run inside COMWorkerThread
ComPtr
<
IMMDevice
>
WasapiOutput
::
SearchDevice
(
std
::
string_view
name
)
WasapiOutput
::
SearchDevice
(
IMMDeviceEnumerator
&
enumerator
,
std
::
string_view
name
)
{
const
auto
device_collection
=
EnumAudioEndpoints
(
*
enumerator
);
const
auto
device_collection
=
EnumAudioEndpoints
(
enumerator
);
const
UINT
count
=
GetCount
(
*
device_collection
);
for
(
UINT
i
=
0
;
i
<
count
;
++
i
)
{
...
...
@@ -885,7 +1060,11 @@ WasapiOutput::SearchDevice(std::string_view name)
return
nullptr
;
}
static
bool
wasapi_output_test_default_device
()
{
return
true
;
}
static
bool
wasapi_output_test_default_device
()
{
return
true
;
}
const
struct
AudioOutputPlugin
wasapi_output_plugin
=
{
"wasapi"
,
...
...
src/win32/Com.hxx
View file @
25354b9d
...
...
@@ -29,17 +29,9 @@
class
COM
{
public
:
COM
()
{
if
(
HRESULT
result
=
CoInitializeEx
(
nullptr
,
COINIT_
MULTITHREADED
);
if
(
HRESULT
result
=
CoInitializeEx
(
nullptr
,
COINIT_
APARTMENTTHREADED
|
COINIT_DISABLE_OLE1DDE
);
FAILED
(
result
))
{
throw
FormatHResultError
(
result
,
"Unable to initialize COM with COINIT_MULTITHREADED"
);
}
}
COM
(
bool
)
{
if
(
HRESULT
result
=
CoInitializeEx
(
nullptr
,
COINIT_APARTMENTTHREADED
);
FAILED
(
result
))
{
throw
FormatHResultError
(
throw
MakeHResultError
(
result
,
"Unable to initialize COM with COINIT_APARTMENTTHREADED"
);
}
...
...
src/win32/ComPtr.hxx
View file @
25354b9d
...
...
@@ -85,7 +85,7 @@ public:
::
CoCreateInstance
(
class_id
,
unknown_outer
,
class_context
,
__uuidof
(
T
),
reinterpret_cast
<
void
**>
(
&
ptr
));
if
(
FAILED
(
result
))
{
throw
Format
HResultError
(
result
,
"Unable to create instance"
);
throw
Make
HResultError
(
result
,
"Unable to create instance"
);
}
}
...
...
src/win32/ComWorker.cxx
View file @
25354b9d
...
...
@@ -21,13 +21,11 @@
#include "Com.hxx"
#include "thread/Name.hxx"
Mutex
COMWorker
::
mutex
;
unsigned
int
COMWorker
::
reference_count
=
0
;
std
::
optional
<
COMWorker
::
COMWorkerThread
>
COMWorker
::
thread
;
void
COMWorker
::
COMWorkerThread
::
Work
()
noexcept
{
void
COMWorker
::
Work
()
noexcept
{
SetThreadName
(
"COM Worker"
);
COM
com
{
true
}
;
COM
com
;
while
(
true
)
{
if
(
!
running_flag
.
test_and_set
())
{
return
;
...
...
src/win32/ComWorker.hxx
View file @
25354b9d
...
...
@@ -22,76 +22,46 @@
#include "WinEvent.hxx"
#include "thread/Future.hxx"
#include "thread/Mutex.hxx"
#include "thread/Thread.hxx"
#include <boost/lockfree/spsc_queue.hpp>
#include <mutex>
#include <optional>
#include <windows.h>
// Worker thread for all COM operation
class
COMWorker
{
private
:
class
COMWorkerThread
:
public
Thread
{
public
:
COMWorkerThread
()
:
Thread
{
BIND_THIS_METHOD
(
Work
)}
{}
private
:
friend
class
COMWorker
;
void
Work
()
noexcept
;
void
Finish
()
noexcept
{
running_flag
.
clear
();
event
.
Set
();
}
void
Push
(
const
std
::
function
<
void
()
>
&
function
)
{
spsc_buffer
.
push
(
function
);
event
.
Set
();
}
Thread
thread
{
BIND_THIS_METHOD
(
Work
)};
boost
::
lockfree
::
spsc_queue
<
std
::
function
<
void
()
>>
spsc_buffer
{
32
};
std
::
atomic_flag
running_flag
=
true
;
WinEvent
event
{};
};
boost
::
lockfree
::
spsc_queue
<
std
::
function
<
void
()
>>
spsc_buffer
{
32
};
std
::
atomic_flag
running_flag
=
true
;
WinEvent
event
{};
public
:
static
void
Aquire
()
{
std
::
unique_lock
locker
(
mutex
);
if
(
reference_count
==
0
)
{
thread
.
emplace
();
thread
->
Start
();
}
++
reference_count
;
COMWorker
()
{
thread
.
Start
();
}
static
void
Release
()
noexcept
{
std
::
unique_lock
locker
(
mutex
);
--
reference_count
;
if
(
reference_count
==
0
)
{
thread
->
Finish
();
thread
->
Join
();
thread
.
reset
();
}
~
COMWorker
()
noexcept
{
Finish
();
thread
.
Join
();
}
template
<
typename
Function
,
typename
...
Args
>
static
auto
Async
(
Function
&&
function
,
Args
&&
...
args
)
{
using
R
=
std
::
invoke_result_t
<
std
::
decay_t
<
Function
>
,
std
::
decay_t
<
Args
>
...
>
;
COMWorker
(
const
COMWorker
&
)
=
delete
;
COMWorker
&
operator
=
(
const
COMWorker
&
)
=
delete
;
template
<
typename
Function
>
auto
Async
(
Function
&&
function
)
{
using
R
=
std
::
invoke_result_t
<
std
::
decay_t
<
Function
>>
;
auto
promise
=
std
::
make_shared
<
Promise
<
R
>>
();
auto
future
=
promise
->
get_future
();
thread
->
Push
([
function
=
std
::
forward
<
Function
>
(
function
),
args
=
std
::
make_tuple
(
std
::
forward
<
Args
>
(
args
)...),
Push
([
function
=
std
::
forward
<
Function
>
(
function
),
promise
=
std
::
move
(
promise
)]()
mutable
{
try
{
if
constexpr
(
std
::
is_void_v
<
R
>
)
{
std
::
apply
(
std
::
forward
<
Function
>
(
function
),
std
::
move
(
args
));
std
::
invoke
(
std
::
forward
<
Function
>
(
function
));
promise
->
set_value
();
}
else
{
promise
->
set_value
(
std
::
apply
(
std
::
forward
<
Function
>
(
function
),
std
::
move
(
args
)));
promise
->
set_value
(
std
::
invoke
(
std
::
forward
<
Function
>
(
function
)));
}
}
catch
(...)
{
promise
->
set_exception
(
std
::
current_exception
());
...
...
@@ -101,9 +71,17 @@ public:
}
private
:
static
Mutex
mutex
;
static
unsigned
int
reference_count
;
static
std
::
optional
<
COMWorkerThread
>
thread
;
void
Finish
()
noexcept
{
running_flag
.
clear
();
event
.
Set
();
}
void
Push
(
const
std
::
function
<
void
()
>
&
function
)
{
spsc_buffer
.
push
(
function
);
event
.
Set
();
}
void
Work
()
noexcept
;
};
#endif
src/win32/HResult.cxx
View file @
25354b9d
...
...
@@ -18,6 +18,7 @@
*/
#include "HResult.hxx"
#include "system/Error.hxx"
#include <cassert>
#include <cstdarg>
...
...
@@ -27,11 +28,21 @@
std
::
string
HResultCategory
::
message
(
int
Errcode
)
const
{
char
buffer
[
256
];
/* FormatMessage() supports some HRESULT values (depending on
the Windows version) */
if
(
FormatMessageA
(
FORMAT_MESSAGE_FROM_SYSTEM
|
FORMAT_MESSAGE_IGNORE_INSERTS
,
nullptr
,
Errcode
,
0
,
buffer
,
sizeof
(
buffer
),
nullptr
))
return
buffer
;
const
auto
msg
=
HRESULTToString
(
Errcode
);
if
(
!
msg
.
empty
())
return
std
::
string
(
msg
);
char
buffer
[
11
];
// "0x12345678\0"
int
size
=
snprintf
(
buffer
,
sizeof
(
buffer
),
"0x%1x"
,
Errcode
);
assert
(
2
<=
size
&&
size
<=
10
);
return
std
::
string
(
buffer
,
size
);
...
...
src/win32/HResult.hxx
View file @
25354b9d
...
...
@@ -50,6 +50,8 @@ case x:
C
(
AUDCLNT_E_SERVICE_NOT_RUNNING
);
C
(
AUDCLNT_E_UNSUPPORTED_FORMAT
);
C
(
AUDCLNT_E_WRONG_ENDPOINT_TYPE
);
C
(
AUDCLNT_E_NOT_INITIALIZED
);
C
(
AUDCLNT_E_NOT_STOPPED
);
C
(
CO_E_NOTINITIALIZED
);
C
(
E_INVALIDARG
);
C
(
E_OUTOFMEMORY
);
...
...
@@ -74,6 +76,13 @@ static inline const std::error_category &hresult_category() noexcept {
return
hresult_category_instance
;
}
inline
std
::
system_error
MakeHResultError
(
HRESULT
result
,
const
char
*
msg
)
noexcept
{
return
std
::
system_error
(
std
::
error_code
(
result
,
hresult_category
()),
msg
);
}
gcc_printf
(
2
,
3
)
std
::
system_error
FormatHResultError
(
HRESULT
result
,
const
char
*
fmt
,
...)
noexcept
;
...
...
test/run_convert.cxx
View file @
25354b9d
...
...
@@ -29,6 +29,7 @@
#include "pcm/Convert.hxx"
#include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "io/FileDescriptor.hxx"
#include "util/ConstBuffer.hxx"
#include "util/StaticFifoBuffer.hxx"
#include "util/OptionDef.hxx"
...
...
@@ -101,26 +102,21 @@ public:
}
};
int
main
(
int
argc
,
char
**
argv
)
try
{
const
auto
c
=
ParseCommandLine
(
argc
,
argv
);
SetLogThreshold
(
c
.
verbose
?
LogLevel
::
DEBUG
:
LogLevel
::
INFO
);
const
GlobalInit
init
(
c
.
config_path
);
const
size_t
in_frame_size
=
c
.
in_audio_format
.
GetFrameSize
();
PcmConvert
state
(
c
.
in_audio_format
,
c
.
out_audio_format
);
static
void
RunConvert
(
PcmConvert
&
convert
,
size_t
in_frame_size
,
FileDescriptor
in_fd
,
FileDescriptor
out_fd
)
{
in_fd
.
SetBinaryMode
();
out_fd
.
SetBinaryMode
();
StaticFifoBuffer
<
uint8_t
,
4096
>
buffer
;
StaticFifoBuffer
<
std
::
byte
,
4096
>
buffer
;
while
(
true
)
{
{
const
auto
dest
=
buffer
.
Write
();
assert
(
!
dest
.
empty
());
ssize_t
nbytes
=
read
(
0
,
dest
.
data
,
dest
.
size
);
ssize_t
nbytes
=
in_fd
.
Read
(
dest
.
data
,
dest
.
size
);
if
(
nbytes
<=
0
)
break
;
...
...
@@ -136,20 +132,31 @@ try {
buffer
.
Consume
(
src
.
size
);
auto
output
=
state
.
Convert
({
src
.
data
,
src
.
size
});
[[
maybe_unused
]]
ssize_t
ignored
=
write
(
1
,
output
.
data
,
output
.
size
);
auto
output
=
convert
.
Convert
({
src
.
data
,
src
.
size
});
out_fd
.
FullWrite
(
output
.
data
,
output
.
size
);
}
while
(
true
)
{
auto
output
=
state
.
Flush
();
auto
output
=
convert
.
Flush
();
if
(
output
.
IsNull
())
break
;
[[
maybe_unused
]]
ssize_t
ignored
=
write
(
1
,
output
.
data
,
output
.
size
);
out_fd
.
FullWrite
(
output
.
data
,
output
.
size
);
}
}
int
main
(
int
argc
,
char
**
argv
)
try
{
const
auto
c
=
ParseCommandLine
(
argc
,
argv
);
SetLogThreshold
(
c
.
verbose
?
LogLevel
::
DEBUG
:
LogLevel
::
INFO
);
const
GlobalInit
init
(
c
.
config_path
);
PcmConvert
state
(
c
.
in_audio_format
,
c
.
out_audio_format
);
RunConvert
(
state
,
c
.
in_audio_format
.
GetFrameSize
(),
FileDescriptor
(
STDIN_FILENO
),
FileDescriptor
(
STDOUT_FILENO
));
return
EXIT_SUCCESS
;
}
catch
(...)
{
...
...
test/run_input.cxx
View file @
25354b9d
...
...
@@ -164,6 +164,8 @@ static int
dump_input_stream
(
InputStream
&
is
,
FileDescriptor
out
,
offset_type
seek
,
size_t
chunk_size
)
{
out
.
SetBinaryMode
();
std
::
unique_lock
<
Mutex
>
lock
(
is
.
mutex
);
if
(
seek
>
0
)
...
...
test/run_output.cxx
View file @
25354b9d
...
...
@@ -31,6 +31,7 @@
#include "util/StringBuffer.hxx"
#include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx"
#include "util/StaticFifoBuffer.hxx"
#include "util/PrintException.hxx"
#include "LogBackend.hxx"
...
...
@@ -113,8 +114,11 @@ LoadAudioOutput(const ConfigData &config, EventLoop &event_loop,
}
static
void
run_output
(
AudioOutput
&
ao
,
AudioFormat
audio_format
)
RunOutput
(
AudioOutput
&
ao
,
AudioFormat
audio_format
,
FileDescriptor
in_fd
)
{
in_fd
.
SetBinaryMode
();
/* open the audio output */
ao
.
Enable
();
...
...
@@ -126,33 +130,40 @@ run_output(AudioOutput &ao, AudioFormat audio_format)
fprintf
(
stderr
,
"audio_format=%s
\n
"
,
ToString
(
audio_format
).
c_str
());
size_t
frame_size
=
audio_format
.
GetFrameSize
();
const
size_t
in_
frame_size
=
audio_format
.
GetFrameSize
();
/* play */
size_t
length
=
0
;
char
buffer
[
4096
];
StaticFifoBuffer
<
std
::
byte
,
4096
>
buffer
;
while
(
true
)
{
if
(
length
<
sizeof
(
buffer
))
{
ssize_t
nbytes
=
read
(
0
,
buffer
+
length
,
sizeof
(
buffer
)
-
length
);
{
const
auto
dest
=
buffer
.
Write
();
assert
(
!
dest
.
empty
());
ssize_t
nbytes
=
in_fd
.
Read
(
dest
.
data
,
dest
.
size
);
if
(
nbytes
<=
0
)
break
;
length
+=
(
size_t
)
nbytes
;
buffer
.
Append
(
nbytes
)
;
}
size_t
play_length
=
(
length
/
frame_size
)
*
frame_size
;
if
(
play_length
>
0
)
{
size_t
consumed
=
ao
.
Play
(
buffer
,
play_length
);
auto
src
=
buffer
.
Read
();
assert
(
!
src
.
empty
());
assert
(
consumed
<=
length
);
assert
(
consumed
%
frame_size
==
0
);
src
.
size
-=
src
.
size
%
in_frame_size
;
if
(
src
.
empty
())
continue
;
length
-=
consumed
;
memmove
(
buffer
,
buffer
+
consumed
,
length
);
}
size_t
consumed
=
ao
.
Play
(
src
.
data
,
src
.
size
);
assert
(
consumed
<=
src
.
size
);
assert
(
consumed
%
in_frame_size
==
0
);
buffer
.
Consume
(
consumed
);
}
ao
.
Drain
();
}
int
main
(
int
argc
,
char
**
argv
)
...
...
@@ -174,7 +185,7 @@ try {
/* do it */
run_output
(
*
ao
,
c
.
audio_format
);
RunOutput
(
*
ao
,
c
.
audio_format
,
FileDescriptor
(
STDIN_FILENO
)
);
/* cleanup and exit */
...
...
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