commit 6c9c48853e5012285bfd0d04094d9c2c948db23a
parent 86ed3d35616d58be660771325d65f2e1e3abb0ca
Author: Henry Wilson <henry@henryandlizzy.uk>
Date: Fri, 26 Aug 2022 23:40:35 +0100
pulse: Add pulse async client
Diffstat:
3 files changed, 171 insertions(+), 1 deletion(-)
diff --git a/makefile b/makefile
@@ -18,8 +18,9 @@ clean:
$(RM) $(c_targets) $(cpp_targets)
aio: -lrt
+gl-asteroids: -lglfw -lGL -lm
io_uring: -luring
+pulse-async-client: -lpulse
pulse-simple-client: -lpulse-simple -lm
-gl-asteroids: -lglfw -lGL -lm
.PHONY: clean all all-c all-cpp
diff --git a/res/pluck.mono44100s16le b/res/pluck.mono44100s16le
Binary files differ.
diff --git a/src/pulse-async-client.cpp b/src/pulse-async-client.cpp
@@ -0,0 +1,169 @@
+#include <pulse/pulseaudio.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <vector>
+#include <span>
+#include <memory>
+#include <fstream>
+
+bool ready = false;
+
+std::vector<short> sample;
+std::vector<short>::const_iterator pos;
+
+void play_sample(std::span<short> buf)
+{
+ for (auto& s : buf)
+ {
+ if (pos != sample.cend())
+ s = *pos++;
+ else
+ s = 0;
+ }
+}
+
+void wavegen(std::span<short> buf)
+{
+ static unsigned i;
+ for (auto& s : buf)
+ {
+ unsigned x;
+
+ if (i > 100)
+ x = 100 - i;
+ else
+ x = i;
+
+ x *= 2 * SHRT_MAX / 100;
+ s = x - SHRT_MAX;
+
+ if (++i > 200)
+ i = 0;
+ }
+}
+
+void pulse_state_cb(pa_context* c, void*)
+{
+ std::cout << "pulse state = " << pa_context_get_state(c) << '\n';
+ switch (pa_context_get_state(c))
+ {
+ case PA_CONTEXT_READY:
+ ready = true;
+ break;
+ }
+}
+
+void write_cb(pa_stream* s, size_t, void*)
+{
+ void* buf;
+ size_t len = -1;
+ pa_stream_begin_write(s, &buf, &len);
+
+ auto v = (short*)buf;
+ play_sample({v, len/2});
+ pa_stream_write(s, buf, len, nullptr, 0, PA_SEEK_RELATIVE);
+}
+
+void underflow_cb(pa_stream* s, void*)
+{
+ std::cout << __func__ << '\n';
+}
+
+struct mainloop
+{
+ mainloop(void)
+ : h(pa_mainloop_new(), &pa_mainloop_free)
+ {
+ assert(h);
+ }
+
+ pa_mainloop_api* get_api(void)
+ {
+ return pa_mainloop_get_api(h.get());
+ }
+
+ operator pa_mainloop*(void)
+ {
+ return h.get();
+ }
+
+private:
+ std::unique_ptr<pa_mainloop, void(*)(pa_mainloop*)> h;
+};
+
+struct context
+{
+ context(pa_mainloop_api* api, char const* name)
+ : h(pa_context_new_with_proplist(api, name, nullptr), pa_context_unref)
+ {
+ assert(h);
+ }
+
+ operator pa_context*(void)
+ {
+ return h.get();
+ }
+
+private:
+ std::unique_ptr<pa_context, void(*)(pa_context*)> h;
+};
+
+int main(void)
+{
+ {
+ std::ifstream file{"res/pluck.mono44100s16le", std::ios::binary | std::ios::ate};
+ assert(file);
+ auto size = file.tellg();
+ sample.resize(size/2);
+ file.seekg(0);
+ assert(file.read((char*)sample.data(), size));
+ pos = sample.cbegin();
+ }
+
+ int latency = 20000;
+
+ mainloop mainloop{};
+ auto api = mainloop.get_api();
+
+ context ctx{api, "pulse-test"};
+ assert(ctx);
+
+ pa_context_set_state_callback(ctx, pulse_state_cb, nullptr);
+ if (pa_context_connect(ctx, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr))
+ perror("pa_context_connect");
+
+ while(not ready)
+ pa_mainloop_iterate(mainloop, 0, nullptr);
+
+ pa_sample_spec ss =
+ {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 44100,
+ .channels = 1,
+ };
+ auto playstream = pa_stream_new(ctx, "playback", &ss, nullptr);
+ assert(playstream);
+
+ pa_stream_set_write_callback(playstream, write_cb, nullptr);
+ pa_stream_set_underflow_callback(playstream, underflow_cb, nullptr);
+
+ uint32_t len = pa_usec_to_bytes(latency, &ss);
+ uint32_t minreq = pa_usec_to_bytes(0, &ss);
+ pa_buffer_attr bufattr =
+ {
+ .maxlength = (uint32_t)-1,
+ .tlength = (uint32_t)-1,
+ .prebuf = (uint32_t)-1,
+ .minreq = (uint32_t)-1,
+ .fragsize = (uint32_t)-1,
+ };
+ auto r = pa_stream_connect_playback(playstream, nullptr, &bufattr, PA_STREAM_NOFLAGS /*PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE*/, nullptr, nullptr);
+ assert(not r);
+
+ pa_mainloop_run(mainloop, nullptr);
+
+ pa_context_disconnect(ctx);
+
+ return 0;
+}