From 25b01940369d746debd0240bcab617b3451aa8c3 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Wed, 10 Mar 2021 20:47:15 +0100
Subject: [PATCH] output/wasapi: implement Drain()

---
 .../plugins/wasapi/WasapiOutputPlugin.cxx     | 36 +++++++++++++++++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
index 203896c7a..fed16fbee 100644
--- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
+++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx
@@ -181,6 +181,8 @@ class WasapiOutputThread {
 
 	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 =
@@ -224,6 +226,8 @@ public:
 	}
 
 	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);
@@ -236,6 +240,24 @@ public:
 		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,
@@ -417,6 +439,7 @@ try {
 		if (cancel.load()) {
 			spsc_buffer.consume_all([](auto &&) {});
 			cancel.store(false);
+			empty.store(true);
 			InterruptWaiter();
 		}
 
@@ -475,6 +498,9 @@ try {
 		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();
@@ -741,9 +767,15 @@ WasapiOutput::Drain()
 {
 	assert(thread);
 
-	// TODO implement
+	not_interrupted.test_and_set();
 
-	thread->CheckException();
+	while (!thread->Drain()) {
+		if (!not_interrupted.test_and_set())
+			throw AudioOutputInterrupted{};
+	}
+
+	/* TODO: this needs to wait until the hardware has really
+	   finished playing */
 }
 
 void