Commit 25354b9d authored by Max Kellermann's avatar Max Kellermann

Merge branch 'v0.22.x'

parents ee720064 25b01940
...@@ -172,7 +172,15 @@ FileDescriptor::CreatePipe(FileDescriptor &r, FileDescriptor &w) noexcept ...@@ -172,7 +172,15 @@ FileDescriptor::CreatePipe(FileDescriptor &r, FileDescriptor &w) noexcept
#endif #endif
} }
#ifndef _WIN32 #ifdef _WIN32
void
FileDescriptor::SetBinaryMode() noexcept
{
_setmode(fd, _O_BINARY);
}
#else // !_WIN32
bool bool
FileDescriptor::CreatePipeNonBlock(FileDescriptor &r, FileDescriptor::CreatePipeNonBlock(FileDescriptor &r,
......
...@@ -147,10 +147,13 @@ public: ...@@ -147,10 +147,13 @@ public:
#ifdef _WIN32 #ifdef _WIN32
void EnableCloseOnExec() noexcept {} void EnableCloseOnExec() noexcept {}
void DisableCloseOnExec() noexcept {} void DisableCloseOnExec() noexcept {}
void SetBinaryMode() noexcept;
#else #else
static bool CreatePipeNonBlock(FileDescriptor &r, static bool CreatePipeNonBlock(FileDescriptor &r,
FileDescriptor &w) noexcept; FileDescriptor &w) noexcept;
void SetBinaryMode() noexcept {}
/** /**
* Enable non-blocking mode on this file descriptor. * Enable non-blocking mode on this file descriptor.
*/ */
......
...@@ -44,7 +44,11 @@ public: ...@@ -44,7 +44,11 @@ public:
void Close() noexcept override {} void Close() noexcept override {}
int GetVolume() 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; HRESULT result;
float volume_level; float volume_level;
...@@ -55,9 +59,9 @@ public: ...@@ -55,9 +59,9 @@ public:
result = endpoint_volume->GetMasterVolumeLevelScalar( result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level); &volume_level);
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError(result, throw MakeHResultError(result,
"Unable to get master " "Unable to get master "
"volume level"); "volume level");
} }
} else { } else {
auto session_volume = auto session_volume =
...@@ -65,7 +69,7 @@ public: ...@@ -65,7 +69,7 @@ public:
result = session_volume->GetMasterVolume(&volume_level); result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError( throw MakeHResultError(
result, "Unable to get master volume"); result, "Unable to get master volume");
} }
} }
...@@ -76,7 +80,11 @@ public: ...@@ -76,7 +80,11 @@ public:
} }
void SetVolume(unsigned volume) override { 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; HRESULT result;
const float volume_level = volume / 100.0f; const float volume_level = volume / 100.0f;
...@@ -87,7 +95,7 @@ public: ...@@ -87,7 +95,7 @@ public:
result = endpoint_volume->SetMasterVolumeLevelScalar( result = endpoint_volume->SetMasterVolumeLevelScalar(
volume_level, nullptr); volume_level, nullptr);
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError( throw MakeHResultError(
result, result,
"Unable to set master volume level"); "Unable to set master volume level");
} }
...@@ -98,7 +106,7 @@ public: ...@@ -98,7 +106,7 @@ public:
result = session_volume->SetMasterVolume(volume_level, result = session_volume->SetMasterVolume(volume_level,
nullptr); nullptr);
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError( throw MakeHResultError(
result, "Unable to set master volume"); result, "Unable to set master volume");
} }
} }
......
...@@ -33,8 +33,8 @@ GetBufferSizeInFrames(IAudioClient &client) ...@@ -33,8 +33,8 @@ GetBufferSizeInFrames(IAudioClient &client)
HRESULT result = client.GetBufferSize(&buffer_size_in_frames); HRESULT result = client.GetBufferSize(&buffer_size_in_frames);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, throw MakeHResultError(result,
"Unable to get audio client buffer size"); "Unable to get audio client buffer size");
return buffer_size_in_frames; return buffer_size_in_frames;
} }
...@@ -46,8 +46,8 @@ GetCurrentPaddingFrames(IAudioClient &client) ...@@ -46,8 +46,8 @@ GetCurrentPaddingFrames(IAudioClient &client)
HRESULT result = client.GetCurrentPadding(&padding_frames); HRESULT result = client.GetCurrentPadding(&padding_frames);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, throw MakeHResultError(result,
"Failed to get current padding"); "Failed to get current padding");
return padding_frames; return padding_frames;
} }
...@@ -59,7 +59,7 @@ GetMixFormat(IAudioClient &client) ...@@ -59,7 +59,7 @@ GetMixFormat(IAudioClient &client)
HRESULT result = client.GetMixFormat(&f); HRESULT result = client.GetMixFormat(&f);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "GetMixFormat failed"); throw MakeHResultError(result, "GetMixFormat failed");
return ComHeapPtr{f}; return ComHeapPtr{f};
} }
...@@ -69,7 +69,7 @@ Start(IAudioClient &client) ...@@ -69,7 +69,7 @@ Start(IAudioClient &client)
{ {
HRESULT result = client.Start(); HRESULT result = client.Start();
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Failed to start client"); throw MakeHResultError(result, "Failed to start client");
} }
inline void inline void
...@@ -77,7 +77,7 @@ Stop(IAudioClient &client) ...@@ -77,7 +77,7 @@ Stop(IAudioClient &client)
{ {
HRESULT result = client.Stop(); HRESULT result = client.Stop();
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Failed to stop client"); throw MakeHResultError(result, "Failed to stop client");
} }
inline void inline void
...@@ -85,7 +85,7 @@ SetEventHandle(IAudioClient &client, HANDLE h) ...@@ -85,7 +85,7 @@ SetEventHandle(IAudioClient &client, HANDLE h)
{ {
HRESULT result = client.SetEventHandle(h); HRESULT result = client.SetEventHandle(h);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Unable to set event handle"); throw MakeHResultError(result, "Unable to set event handle");
} }
template<typename T> template<typename T>
...@@ -95,7 +95,7 @@ GetService(IAudioClient &client) ...@@ -95,7 +95,7 @@ GetService(IAudioClient &client)
T *p = nullptr; T *p = nullptr;
HRESULT result = client.GetService(IID_PPV_ARGS(&p)); HRESULT result = client.GetService(IID_PPV_ARGS(&p));
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Unable to get service"); throw MakeHResultError(result, "Unable to get service");
return ComPtr{p}; return ComPtr{p};
} }
......
...@@ -33,8 +33,8 @@ GetDefaultAudioEndpoint(IMMDeviceEnumerator &e) ...@@ -33,8 +33,8 @@ GetDefaultAudioEndpoint(IMMDeviceEnumerator &e)
HRESULT result = e.GetDefaultAudioEndpoint(eRender, eMultimedia, HRESULT result = e.GetDefaultAudioEndpoint(eRender, eMultimedia,
&device); &device);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, throw MakeHResultError(result,
"Unable to get default device for multimedia"); "Unable to get default device for multimedia");
return ComPtr{device}; return ComPtr{device};
} }
...@@ -47,7 +47,7 @@ EnumAudioEndpoints(IMMDeviceEnumerator &e) ...@@ -47,7 +47,7 @@ EnumAudioEndpoints(IMMDeviceEnumerator &e)
HRESULT result = e.EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, HRESULT result = e.EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
&dc); &dc);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Unable to enumerate devices"); throw MakeHResultError(result, "Unable to enumerate devices");
return ComPtr{dc}; return ComPtr{dc};
} }
...@@ -59,7 +59,7 @@ GetCount(IMMDeviceCollection &dc) ...@@ -59,7 +59,7 @@ GetCount(IMMDeviceCollection &dc)
HRESULT result = dc.GetCount(&count); HRESULT result = dc.GetCount(&count);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Collection->GetCount failed"); throw MakeHResultError(result, "Collection->GetCount failed");
return count; return count;
} }
...@@ -71,7 +71,7 @@ Item(IMMDeviceCollection &dc, UINT i) ...@@ -71,7 +71,7 @@ Item(IMMDeviceCollection &dc, UINT i)
auto result = dc.Item(i, &device); auto result = dc.Item(i, &device);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Collection->Item failed"); throw MakeHResultError(result, "Collection->Item failed");
return ComPtr{device}; return ComPtr{device};
} }
...@@ -83,7 +83,7 @@ GetState(IMMDevice &device) ...@@ -83,7 +83,7 @@ GetState(IMMDevice &device)
HRESULT result = device.GetState(&state);; HRESULT result = device.GetState(&state);;
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Unable to get device status"); throw MakeHResultError(result, "Unable to get device status");
return state; return state;
} }
...@@ -96,7 +96,7 @@ Activate(IMMDevice &device) ...@@ -96,7 +96,7 @@ Activate(IMMDevice &device)
HRESULT result = device.Activate(__uuidof(T), CLSCTX_ALL, HRESULT result = device.Activate(__uuidof(T), CLSCTX_ALL,
nullptr, (void **)&p); nullptr, (void **)&p);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, "Unable to activate device"); throw MakeHResultError(result, "Unable to activate device");
return ComPtr{p}; return ComPtr{p};
} }
...@@ -108,8 +108,8 @@ OpenPropertyStore(IMMDevice &device) ...@@ -108,8 +108,8 @@ OpenPropertyStore(IMMDevice &device)
HRESULT result = device.OpenPropertyStore(STGM_READ, &property_store); HRESULT result = device.OpenPropertyStore(STGM_READ, &property_store);
if (FAILED(result)) if (FAILED(result))
throw FormatHResultError(result, throw MakeHResultError(result,
"Device->OpenPropertyStore failed"); "Device->OpenPropertyStore failed");
return ComPtr{property_store}; return ComPtr{property_store};
} }
......
...@@ -20,10 +20,13 @@ ...@@ -20,10 +20,13 @@
#ifndef MPD_WASAPI_OUTPUT_FOR_MIXER_HXX #ifndef MPD_WASAPI_OUTPUT_FOR_MIXER_HXX
#define MPD_WASAPI_OUTPUT_FOR_MIXER_HXX #define MPD_WASAPI_OUTPUT_FOR_MIXER_HXX
#include <memory>
struct IMMDevice; struct IMMDevice;
struct IAudioClient; struct IAudioClient;
class AudioOutput; class AudioOutput;
class WasapiOutput; class WasapiOutput;
class COMWorker;
[[gnu::pure]] [[gnu::pure]]
WasapiOutput & WasapiOutput &
...@@ -34,6 +37,10 @@ bool ...@@ -34,6 +37,10 @@ bool
wasapi_is_exclusive(WasapiOutput &output) noexcept; wasapi_is_exclusive(WasapiOutput &output) noexcept;
[[gnu::pure]] [[gnu::pure]]
std::shared_ptr<COMWorker>
wasapi_output_get_com_worker(WasapiOutput &output) noexcept;
[[gnu::pure]]
IMMDevice * IMMDevice *
wasapi_output_get_device(WasapiOutput &output) noexcept; wasapi_output_get_device(WasapiOutput &output) noexcept;
......
...@@ -29,17 +29,9 @@ ...@@ -29,17 +29,9 @@
class COM { class COM {
public: public:
COM() { COM() {
if (HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE);
FAILED(result)) { FAILED(result)) {
throw FormatHResultError( throw MakeHResultError(
result,
"Unable to initialize COM with COINIT_MULTITHREADED");
}
}
COM(bool) {
if (HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
FAILED(result)) {
throw FormatHResultError(
result, result,
"Unable to initialize COM with COINIT_APARTMENTTHREADED"); "Unable to initialize COM with COINIT_APARTMENTTHREADED");
} }
......
...@@ -85,7 +85,7 @@ public: ...@@ -85,7 +85,7 @@ public:
::CoCreateInstance(class_id, unknown_outer, class_context, ::CoCreateInstance(class_id, unknown_outer, class_context,
__uuidof(T), reinterpret_cast<void **>(&ptr)); __uuidof(T), reinterpret_cast<void **>(&ptr));
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError(result, "Unable to create instance"); throw MakeHResultError(result, "Unable to create instance");
} }
} }
......
...@@ -21,13 +21,11 @@ ...@@ -21,13 +21,11 @@
#include "Com.hxx" #include "Com.hxx"
#include "thread/Name.hxx" #include "thread/Name.hxx"
Mutex COMWorker::mutex; void
unsigned int COMWorker::reference_count = 0; COMWorker::Work() noexcept
std::optional<COMWorker::COMWorkerThread> COMWorker::thread; {
void COMWorker::COMWorkerThread::Work() noexcept {
SetThreadName("COM Worker"); SetThreadName("COM Worker");
COM com{true}; COM com;
while (true) { while (true) {
if (!running_flag.test_and_set()) { if (!running_flag.test_and_set()) {
return; return;
......
...@@ -22,76 +22,46 @@ ...@@ -22,76 +22,46 @@
#include "WinEvent.hxx" #include "WinEvent.hxx"
#include "thread/Future.hxx" #include "thread/Future.hxx"
#include "thread/Mutex.hxx"
#include "thread/Thread.hxx" #include "thread/Thread.hxx"
#include <boost/lockfree/spsc_queue.hpp> #include <boost/lockfree/spsc_queue.hpp>
#include <mutex>
#include <optional>
#include <windows.h> #include <windows.h>
// Worker thread for all COM operation // Worker thread for all COM operation
class COMWorker { class COMWorker {
private: Thread thread{BIND_THIS_METHOD(Work)};
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();
}
boost::lockfree::spsc_queue<std::function<void()>> spsc_buffer{32}; boost::lockfree::spsc_queue<std::function<void()>> spsc_buffer{32};
std::atomic_flag running_flag = true; std::atomic_flag running_flag = true;
WinEvent event{}; WinEvent event{};
};
public: public:
static void Aquire() { COMWorker() {
std::unique_lock locker(mutex); thread.Start();
if (reference_count == 0) {
thread.emplace();
thread->Start();
}
++reference_count;
} }
static void Release() noexcept {
std::unique_lock locker(mutex); ~COMWorker() noexcept {
--reference_count; Finish();
if (reference_count == 0) { thread.Join();
thread->Finish();
thread->Join();
thread.reset();
}
} }
template <typename Function, typename... Args> COMWorker(const COMWorker &) = delete;
static auto Async(Function &&function, Args &&...args) { COMWorker &operator=(const COMWorker &) = delete;
using R = std::invoke_result_t<std::decay_t<Function>,
std::decay_t<Args>...>; 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 promise = std::make_shared<Promise<R>>();
auto future = promise->get_future(); auto future = promise->get_future();
thread->Push([function = std::forward<Function>(function), Push([function = std::forward<Function>(function),
args = std::make_tuple(std::forward<Args>(args)...),
promise = std::move(promise)]() mutable { promise = std::move(promise)]() mutable {
try { try {
if constexpr (std::is_void_v<R>) { if constexpr (std::is_void_v<R>) {
std::apply(std::forward<Function>(function), std::invoke(std::forward<Function>(function));
std::move(args));
promise->set_value(); promise->set_value();
} else { } else {
promise->set_value(std::apply( promise->set_value(std::invoke(std::forward<Function>(function)));
std::forward<Function>(function),
std::move(args)));
} }
} catch (...) { } catch (...) {
promise->set_exception(std::current_exception()); promise->set_exception(std::current_exception());
...@@ -101,9 +71,17 @@ public: ...@@ -101,9 +71,17 @@ public:
} }
private: private:
static Mutex mutex; void Finish() noexcept {
static unsigned int reference_count; running_flag.clear();
static std::optional<COMWorkerThread> thread; event.Set();
}
void Push(const std::function<void()> &function) {
spsc_buffer.push(function);
event.Set();
}
void Work() noexcept;
}; };
#endif #endif
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
*/ */
#include "HResult.hxx" #include "HResult.hxx"
#include "system/Error.hxx"
#include <cassert> #include <cassert>
#include <cstdarg> #include <cstdarg>
...@@ -27,11 +28,21 @@ ...@@ -27,11 +28,21 @@
std::string std::string
HResultCategory::message(int Errcode) const 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); const auto msg = HRESULTToString(Errcode);
if (!msg.empty()) if (!msg.empty())
return std::string(msg); return std::string(msg);
char buffer[11]; // "0x12345678\0"
int size = snprintf(buffer, sizeof(buffer), "0x%1x", Errcode); int size = snprintf(buffer, sizeof(buffer), "0x%1x", Errcode);
assert(2 <= size && size <= 10); assert(2 <= size && size <= 10);
return std::string(buffer, size); return std::string(buffer, size);
......
...@@ -50,6 +50,8 @@ case x: ...@@ -50,6 +50,8 @@ case x:
C(AUDCLNT_E_SERVICE_NOT_RUNNING); C(AUDCLNT_E_SERVICE_NOT_RUNNING);
C(AUDCLNT_E_UNSUPPORTED_FORMAT); C(AUDCLNT_E_UNSUPPORTED_FORMAT);
C(AUDCLNT_E_WRONG_ENDPOINT_TYPE); C(AUDCLNT_E_WRONG_ENDPOINT_TYPE);
C(AUDCLNT_E_NOT_INITIALIZED);
C(AUDCLNT_E_NOT_STOPPED);
C(CO_E_NOTINITIALIZED); C(CO_E_NOTINITIALIZED);
C(E_INVALIDARG); C(E_INVALIDARG);
C(E_OUTOFMEMORY); C(E_OUTOFMEMORY);
...@@ -74,6 +76,13 @@ static inline const std::error_category &hresult_category() noexcept { ...@@ -74,6 +76,13 @@ static inline const std::error_category &hresult_category() noexcept {
return hresult_category_instance; 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 gcc_printf(2, 3) std::system_error
FormatHResultError(HRESULT result, const char *fmt, ...) noexcept; FormatHResultError(HRESULT result, const char *fmt, ...) noexcept;
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "pcm/Convert.hxx" #include "pcm/Convert.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx" #include "fs/NarrowPath.hxx"
#include "io/FileDescriptor.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/StaticFifoBuffer.hxx" #include "util/StaticFifoBuffer.hxx"
#include "util/OptionDef.hxx" #include "util/OptionDef.hxx"
...@@ -101,26 +102,21 @@ public: ...@@ -101,26 +102,21 @@ public:
} }
}; };
int static void
main(int argc, char **argv) RunConvert(PcmConvert &convert, size_t in_frame_size,
try { FileDescriptor in_fd, FileDescriptor out_fd)
const auto c = ParseCommandLine(argc, argv); {
in_fd.SetBinaryMode();
SetLogThreshold(c.verbose ? LogLevel::DEBUG : LogLevel::INFO); out_fd.SetBinaryMode();
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);
StaticFifoBuffer<uint8_t, 4096> buffer; StaticFifoBuffer<std::byte, 4096> buffer;
while (true) { while (true) {
{ {
const auto dest = buffer.Write(); const auto dest = buffer.Write();
assert(!dest.empty()); 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) if (nbytes <= 0)
break; break;
...@@ -136,20 +132,31 @@ try { ...@@ -136,20 +132,31 @@ try {
buffer.Consume(src.size); buffer.Consume(src.size);
auto output = state.Convert({src.data, src.size}); auto output = convert.Convert({src.data, src.size});
out_fd.FullWrite(output.data, output.size);
[[maybe_unused]] ssize_t ignored = write(1, output.data,
output.size);
} }
while (true) { while (true) {
auto output = state.Flush(); auto output = convert.Flush();
if (output.IsNull()) if (output.IsNull())
break; break;
[[maybe_unused]] ssize_t ignored = write(1, output.data, out_fd.FullWrite(output.data, output.size);
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; return EXIT_SUCCESS;
} catch (...) { } catch (...) {
......
...@@ -164,6 +164,8 @@ static int ...@@ -164,6 +164,8 @@ static int
dump_input_stream(InputStream &is, FileDescriptor out, dump_input_stream(InputStream &is, FileDescriptor out,
offset_type seek, size_t chunk_size) offset_type seek, size_t chunk_size)
{ {
out.SetBinaryMode();
std::unique_lock<Mutex> lock(is.mutex); std::unique_lock<Mutex> lock(is.mutex);
if (seek > 0) if (seek > 0)
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/StaticFifoBuffer.hxx"
#include "util/PrintException.hxx" #include "util/PrintException.hxx"
#include "LogBackend.hxx" #include "LogBackend.hxx"
...@@ -113,8 +114,11 @@ LoadAudioOutput(const ConfigData &config, EventLoop &event_loop, ...@@ -113,8 +114,11 @@ LoadAudioOutput(const ConfigData &config, EventLoop &event_loop,
} }
static void 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 */ /* open the audio output */
ao.Enable(); ao.Enable();
...@@ -126,33 +130,40 @@ run_output(AudioOutput &ao, AudioFormat audio_format) ...@@ -126,33 +130,40 @@ run_output(AudioOutput &ao, AudioFormat audio_format)
fprintf(stderr, "audio_format=%s\n", fprintf(stderr, "audio_format=%s\n",
ToString(audio_format).c_str()); ToString(audio_format).c_str());
size_t frame_size = audio_format.GetFrameSize(); const size_t in_frame_size = audio_format.GetFrameSize();
/* play */ /* play */
size_t length = 0; StaticFifoBuffer<std::byte, 4096> buffer;
char buffer[4096];
while (true) { while (true) {
if (length < sizeof(buffer)) { {
ssize_t nbytes = read(0, buffer + length, const auto dest = buffer.Write();
sizeof(buffer) - length); assert(!dest.empty());
ssize_t nbytes = in_fd.Read(dest.data, dest.size);
if (nbytes <= 0) if (nbytes <= 0)
break; break;
length += (size_t)nbytes; buffer.Append(nbytes);
} }
size_t play_length = (length / frame_size) * frame_size; auto src = buffer.Read();
if (play_length > 0) { assert(!src.empty());
size_t consumed = ao.Play(buffer, play_length);
assert(consumed <= length); src.size -= src.size % in_frame_size;
assert(consumed % frame_size == 0); if (src.empty())
continue;
length -= consumed; size_t consumed = ao.Play(src.data, src.size);
memmove(buffer, buffer + consumed, length);
} assert(consumed <= src.size);
assert(consumed % in_frame_size == 0);
buffer.Consume(consumed);
} }
ao.Drain();
} }
int main(int argc, char **argv) int main(int argc, char **argv)
...@@ -174,7 +185,7 @@ try { ...@@ -174,7 +185,7 @@ try {
/* do it */ /* do it */
run_output(*ao, c.audio_format); RunOutput(*ao, c.audio_format, FileDescriptor(STDIN_FILENO));
/* cleanup and exit */ /* cleanup and exit */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment