#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // https://github.com/pulseaudio/pulseaudio/blob/master/src/pulse/simple.c // https://habr.com/en/articles/663352/#linux-and-pulseaudio /// The pa_system structure holds all the PulseAudio structures together struct pa_system { pa_threaded_mainloop* mainloop; pa_context* context; pa_stream* stream; pa_stream_direction_t direction; pa_mainloop_api* api; const void *read_data; size_t read_index, read_length; int operation_success; }; static void context_state_cb(pa_context *c, void *userdata) { std::cout << "context state callback: "; auto *p = static_cast(userdata); switch(pa_context_get_state(c)) { case PA_CONTEXT_READY: std::cout << "Context Ready" << std::endl; pa_threaded_mainloop_signal(p->mainloop, 0); break; case PA_CONTEXT_TERMINATED: std::cout << "Context Terminated" << std::endl; pa_threaded_mainloop_signal(p->mainloop, 0); break; case PA_CONTEXT_FAILED: std::cout << "Context Failed" << std::endl; pa_threaded_mainloop_signal(p->mainloop, 0); break; case PA_CONTEXT_UNCONNECTED: std::cout << "Context Unconnected" << std::endl; break; case PA_CONTEXT_CONNECTING: std::cout << "Context Connecting" << std::endl; break; case PA_CONTEXT_AUTHORIZING: std::cout << "Context Authorizing" << std::endl; break; case PA_CONTEXT_SETTING_NAME: std::cout << "Context Setting Name" << std::endl; break; default: std::cerr << "The fuck?" << std::endl; break; } } static void stream_state_cb(pa_stream* s, void* userdata) { std::cout << "stream state callback: "; auto *p = static_cast(userdata); auto state = pa_stream_get_state(s); switch(state) { case PA_STREAM_READY: std::cout << "Stream Ready" << std::endl; pa_threaded_mainloop_signal(p->mainloop, 0); break; case PA_STREAM_FAILED: std::cout << "Stream Failed" << std::endl; pa_threaded_mainloop_signal(p->mainloop, 0); break; case PA_STREAM_TERMINATED: std::cout << "Stream Terminated" << std::endl; pa_threaded_mainloop_signal(p->mainloop, 0); break; case PA_STREAM_UNCONNECTED: std::cout << "Stream Unconnected" << std::endl; break; case PA_STREAM_CREATING: std::cout << "Stream Creating" << std::endl; break; } } static void stream_request_cb(pa_stream* s, size_t length, void* userdata) { std::cout << "Received stream request callback: " << length << std::endl; auto *p = static_cast(userdata); pa_threaded_mainloop_signal(p->mainloop, 0); } static void stream_latency_update_cb(pa_stream *s, void* userdata) { std::cout << "Received stream latency update callback" << std::endl; auto *p = static_cast(userdata); pa_threaded_mainloop_signal(p->mainloop, 0); } pa_system* pa_system_new( const char* server, const char* name, pa_stream_direction_t dir, const char* dev, const char* stream_name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_buffer_attr *attr, int *rerror) { pa_system *p; p = pa_xnew(pa_system, 1); p->direction = dir; //const char *server = NULL; //const char *name = "This Dick"; //p->direction = PA_STREAM_PLAYBACK; //const char *dev = NULL; //const char *stream_name = "playback"; //static const pa_sample_spec ss = { // .format = PA_SAMPLE_S16LE, // .rate = 44100, // .channels = 2 //}; //const pa_channel_map *map = NULL; //const pa_buffer_attr *attr = NULL; int error = PA_ERR_INTERNAL; int r; if (!(p->mainloop = pa_threaded_mainloop_new())) throw std::runtime_error("Failed to create a PulseAudio mainloop"); // goto fail; p->api = pa_threaded_mainloop_get_api(p->mainloop); // TODO: Research pa_context_new_with_proplist if (!(p->context = pa_context_new( p->api, "Sound Channel?"))) { // goto fail; throw std::runtime_error("Failed to create a context"); } pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN; pa_context_set_state_callback(p->context, context_state_cb, p); if (pa_context_connect(p->context, server, flags, NULL) < 0) { error = pa_context_errno(p->context); // goto fail; throw std::runtime_error(std::format("Failed to connect a context: {}", error)); } pa_threaded_mainloop_lock(p->mainloop); if (pa_threaded_mainloop_start(p->mainloop) < 0) { // goto unlock_and_fail throw std::runtime_error("Failed to start threaded mainloop"); } for (;;) { pa_context_state_t state; state = pa_context_get_state(p->context); if (state == PA_CONTEXT_READY) break; if (!PA_CONTEXT_IS_GOOD(state)) { error = pa_context_errno(p->context); // goto unlock_and_fail throw std::runtime_error(std::format("Context was not good! {}", error)); } // Wait until the context is ready pa_threaded_mainloop_wait(p->mainloop); } if (!(p->stream = pa_stream_new(p->context, stream_name, ss, map))) { error = pa_context_errno(p->context); // goto unlock_and_fail throw std::runtime_error(std::format("Failed to create a stream. Error code {}", error)); } pa_stream_set_state_callback(p->stream, stream_state_cb, p); pa_stream_set_read_callback(p->stream, stream_request_cb, p); pa_stream_set_write_callback(p->stream, stream_request_cb, p); pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p); pa_stream_flags my_flags = static_cast(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); if (p->direction == PA_STREAM_PLAYBACK) r = pa_stream_connect_playback(p->stream, dev, attr, my_flags, NULL, NULL); else r = pa_stream_connect_record(p->stream, dev, attr, my_flags); if (r < 0) { error = pa_context_errno(p->context); // goto unlock_and_fail throw std::runtime_error("Failed to connect the stream."); } for (;;) { pa_stream_state_t state; state = pa_stream_get_state(p->stream); if (state == PA_STREAM_READY) break; if (!PA_STREAM_IS_GOOD(state)) { error = pa_context_errno(p->context); // goto unlock_and_fail throw std::runtime_error("Stream state is bad!"); } pa_threaded_mainloop_wait(p->mainloop); } pa_threaded_mainloop_unlock(p->mainloop); return p; // unlock_and_fail: // pa_threaded_mainloop_unlock(p->mainloop); // fail: // if (rerror) // *rerror = error; // pa_simple_free(p) // return NULL; } void pa_system_free(pa_system* p) { if (p->mainloop) pa_threaded_mainloop_stop(p->mainloop); if (p->stream) pa_stream_unref(p->stream); if (p->context) { pa_context_disconnect(p->context); pa_context_unref(p->context); } if (p->mainloop) pa_threaded_mainloop_free(p->mainloop); pa_xfree(p); } void on_device_sink(pa_context* c, const pa_sink_info* info, int eol, void* userdata) { if (eol != 0) { pa_system* p = static_cast(userdata); pa_threaded_mainloop_signal(p->mainloop, 0); } if (info) { const char* device_id = info->name; std::cout << "Device found: " << device_id << std::endl; } } void on_device_source(pa_context* c, const pa_source_info* info, int eol, void *userdata) { if (eol != 0) { pa_system* p = static_cast(userdata); pa_threaded_mainloop_signal(p->mainloop, 0); } if (info) { const char* device_id = info->name; std::cout << "Device found: " << device_id << std::endl; } } int pa_system_enumerate_devices(pa_system* p) { pa_threaded_mainloop_lock(p->mainloop); pa_operation * op; if (p->direction == PA_STREAM_PLAYBACK) op = pa_context_get_sink_info_list(p->context, on_device_sink, p); else op = pa_context_get_source_info_list(p->context, on_device_source, p); for (;;) { int r = pa_operation_get_state(op); if (r == PA_OPERATION_DONE || r == PA_OPERATION_CANCELLED) break; pa_threaded_mainloop_wait(p->mainloop); } pa_operation_unref(op); pa_threaded_mainloop_unlock(p->mainloop); return 0; } int pa_system_write(pa_system* p, const void* data, size_t length, int *rerror) { pa_threaded_mainloop_lock(p->mainloop); while (length > 0) { size_t l; int r; while(!(l = pa_stream_writable_size(p->stream))) { pa_threaded_mainloop_wait(p->mainloop); } if (l > length) l = length; r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); data = (const uint8_t*) data + l; length -= l; } pa_threaded_mainloop_unlock(p->mainloop); return 0; } int pa_system_read(pa_system* p, void* data, size_t length, int *rerror) { pa_threaded_mainloop_lock(p->mainloop); while (length > 0) { size_t l; while(!p->read_data) { int r; r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); if (p->read_length <= 0) { pa_threaded_mainloop_wait(p->mainloop); } else if (!p->read_data) { // There's a hole in the stream, skip it. // We could generate silence, but that wouldn't work for compressed streams. pa_stream_drop(p->stream); } else p->read_index = 0; } l = p->read_length < length ? p->read_length : length; memcpy(data, (const uint8_t*) p->read_data+p->read_index, l); data = (uint8_t*) data + l; length -= l; p->read_index += l; p->read_length -= l; if (!p->read_length) { int r; r = pa_stream_drop(p->stream); p->read_data = NULL; p->read_length = 0; p->read_index; } } pa_threaded_mainloop_unlock(p->mainloop); return 0; } static void success_cb(pa_stream *s, int success, void * userdata) { pa_system* p = static_cast(userdata); p->operation_success = success; pa_threaded_mainloop_signal(p->mainloop, 0); } int pa_system_drain(pa_system* p, int *rerror) { pa_operation *o = nullptr; pa_threaded_mainloop_lock(p->mainloop); o = pa_stream_drain(p->stream, success_cb, p); if (!o) { throw std::runtime_error("pa_stream_drain did not fill our pa_operation structure!"); } p->operation_success = 0; while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { pa_threaded_mainloop_wait(p->mainloop); } pa_operation_unref(o); pa_threaded_mainloop_unlock(p->mainloop); return 0; // unlock_and_fail: if (o) { pa_operation_cancel(o); pa_operation_unref(o); } pa_threaded_mainloop_unlock(p->mainloop); return -1; } int pa_system_flush(pa_system* p, int *rerror) { pa_operation *o = NULL; pa_threaded_mainloop_lock(p->mainloop); o = pa_stream_flush(p->stream, success_cb, p); p->operation_success = 0; while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) { pa_threaded_mainloop_wait(p->mainloop); } pa_operation_unref(o); pa_threaded_mainloop_unlock(p->mainloop); return 0; } pa_usec_t pa_system_get_latency(pa_system* p, int *rerror) { pa_usec_t t; pa_threaded_mainloop_lock(p->mainloop); for (;;) { int negative; // CHECK_DEAD_GOTO() if (pa_stream_get_latency(p->stream, &t, &negative) >= 0) { if (p->direction == PA_STREAM_RECORD) { pa_usec_t already_read; /* * */ already_read = pa_bytes_to_usec(p->read_index, pa_stream_get_sample_spec(p->stream)); if (!negative) { if (t > already_read) t -= already_read; else t = 0; } } if (negative) t = 0; break; } pa_threaded_mainloop_wait(p->mainloop); } pa_threaded_mainloop_unlock(p->mainloop); return t; } #define BUFSIZE 1024 static void list_sink_info(pa_context* c, pa_sink_info* i, int eol, void* userdata) { if (eol != 0) { auto *p = static_cast(userdata); pa_threaded_mainloop_signal(p->mainloop, 0); } if (i == nullptr) { std::cerr << "Sink info is null!" << std::endl; return; } std::cout << i->name << std::endl; } static void list_server_info(pa_context* c, pa_server_info* i, void* userdata) { auto *p = static_cast(userdata); pa_threaded_mainloop_signal(p->mainloop, 0); } char mix_sample_s16_pcm(char a, char b) { return (a + b) / 2.f; } // this dick struct pcm_s16_data { std::vector buffer; explicit pcm_s16_data(const std::filesystem::path& file_path) { std::ifstream file(file_path, std::ios::binary | std::ios::ate); file.unsetf(std::ios::skipws); file.seekg(0, std::ios::end); std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); buffer.reserve(size); buffer.insert(buffer.begin(), std::istream_iterator(file), std::istream_iterator()); } explicit pcm_s16_data(const std::vector& pcm_buf) { buffer = pcm_buf; } pcm_s16_data operator + (const pcm_s16_data& rhs) const { std::vector sum; auto rhs_buf = rhs.buffer; auto lhs_buf = this->buffer; for (int i = 0; i < std::max(lhs_buf.size(), rhs_buf.size()); i++) { int a = 0; if (i >= lhs_buf.size() && i < rhs_buf.size()) { sum.push_back(rhs_buf[i]); continue; } if (i >= rhs_buf.size() && i < lhs_buf.size()) { sum.push_back(lhs_buf[i]); continue; } sum.push_back(mix_sample_s16_pcm(lhs_buf[i], rhs_buf[i])); } return pcm_s16_data(sum); } }; int main(int argc, char* argv[]) { if (argc == 1 || argv[1] == "-h" || argv[1] == "help") { std::cout << "pacat - Tests playback of raw audio data via PulseAudio API." << std::endl; std::cout << "usage: pacat (presumably output.raw)\n" << std::endl; } //argv[1] = "output.raw"; static const pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2 }; pa_system *s = NULL; int ret = 1; int error; /* Replace STDIN with the specified file if needed. */ if (argc < 4) { std::cerr << "Please provide two input PCM files!" << std::endl; return -1; } pcm_s16_data song = pcm_s16_data(argv[1]); pcm_s16_data sfx = pcm_s16_data(argv[2]); pcm_s16_data blm = pcm_s16_data(argv[3]); pcm_s16_data combined = song + sfx + blm; /*if (argc > 1) { int fd; if ((fd = open(argv[1], O_RDONLY)) < 0) { fprintf(stderr, __FILE__": open() failed: %s\n", strerror(errno)); goto finish; } if (dup2(fd, STDIN_FILENO) < 0) { fprintf(stderr, __FILE__": dup2() failed: %s\n", strerror(errno)); goto finish; } close(fd); }*/ /* Create a new playback stream */ if (!(s = pa_system_new(NULL, argv[0], PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error))) { fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); return -1; //goto finish; } pa_context_get_server_info(s->context, reinterpret_cast(list_server_info), s); pa_context_get_sink_info_list(s->context, reinterpret_cast(list_sink_info), s); pa_system_enumerate_devices(s); if (pa_system_write(s, combined.buffer.data(), combined.buffer.size(), &error) < 0) { fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); goto finish; } /* for (;;) { uint8_t buf[BUFSIZE]; ssize_t r; #if 0 pa_usec_t latency; if ((latency = pa_simple_get_latency(s, &error)) == (pa_usec_t)-1) { fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s\n", pa_strerror(error)); return; } fprintf(stderr, "%0.0f usec \r", (float)latency); #endif if ((r = read(STDIN_FILENO, buf, sizeof(buf))) <= 0) { if (r == 0) break; fprintf(stderr, __FILE__": read() failed: %s\n", strerror(errno)); goto finish; } if (pa_system_write(s, buf, (size_t)r, &error) < 0) { fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); goto finish; } }*/ /* Make sure that every single sample was played */ if (pa_system_drain(s, &error) < 0) { fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); goto finish; } ret = 0; finish: if (s) pa_system_free(s); return ret; }