Work In Progress pacat_advanced program

This commit is contained in:
2024-08-22 21:51:17 -04:00
parent adb4277630
commit 146c21d586

View File

@@ -1,111 +1,495 @@
#include <pulse/mainloop.h>
#include <pulse/mainloop-api.h>
#include <pulse/pulseaudio.h>
#include <pulse/thread-mainloop.h>
#include <iostream>
#include <pulse/error.h>
#include <pulse/volume.h>
#include <pulse/stream.h>
#include <format>
#include <cstring>
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <fcntl.h>
// https://github.com/pulseaudio/pulseaudio/blob/master/src/pulse/simple.c
static void context_state_callback(pa_context *c, void *userdata)
struct pa_system
{
pa_threaded_mainloop* mainloop;
pa_context* context;
pa_stream* stream;
pa_stream_direction_t direction;
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 << "Received context state callback" << std::endl;
auto *p = static_cast<pa_system *>(userdata);
switch(pa_context_get_state(c)) {
case PA_CONTEXT_READY:
case PA_CONTEXT_TERMINATED:
case PA_CONTEXT_FAILED:
pa_threaded_mainloop_signal(p->mainloop, 0);
break;
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
default:
std::cerr << "The fuck?" << std::endl;
break;
}
}
static void stream_state_cb(pa_stream* s, void* userdata)
{
std::cout << "Received stream state callback" << std::endl;
auto *p = static_cast<pa_system *>(userdata);
auto state = pa_stream_get_state(s);
switch(state) {
case PA_STREAM_READY:
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
pa_threaded_mainloop_signal(p->mainloop, 0);
break;
case PA_STREAM_UNCONNECTED:
case PA_STREAM_CREATING:
break;
}
}
static void stream_request_cb(pa_stream* s, size_t length, void* userdata) {
std::cout << "Received stream request callback" << std::endl;
auto *p = static_cast<pa_system *>(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<pa_system*>(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);
//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;
if (!(p->context = pa_context_new(
pa_threaded_mainloop_get_api(p->mainloop),
"Sound Channel?")))
throw std::runtime_error("Failed to create a context");
// goto fail;
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_flags>(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);
}
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_simple_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<pa_system *>(userdata);
p->operation_success = success;
pa_threaded_mainloop_signal(p->mainloop, 0);
}
int pa_system_drain(pa_system* p, int *rerror)
{
pa_operation *o = NULL;
pa_threaded_mainloop_lock(p->mainloop);
o = pa_stream_drain(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;
// 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
int main(int argc, char* argv[]) {
if (argc == 1 || argv[1] == "-h" || argv[1] == "help") {
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 <file> (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 errno;
int ret = 1;
int error;
pa_mainloop *maneloop;
pa_context *ctx;
pa_stream *stream;
const pa_channel_map* map = NULL;
const pa_buffer_attr* attr = NULL;
const char* server = NULL;
const char* name = "This Dick";
pa_stream_direction_t dir = PA_STREAM_PLAYBACK;
const char* dev = NULL;
const char* stream_name = "playback";
if (!(maneloop = pa_mainloop_new()))
throw std::runtime_error("Failed to create a PulseAudio mainloop");
#pragma region Context Creation
if (!(ctx = pa_context_new(
pa_mainloop_get_api(maneloop),
"Sound Channel?")))
throw std::runtime_error("Failed to create a context");
pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN;
if (pa_context_connect(ctx, server, flags, NULL) < 0)
{
errno = pa_context_errno(ctx);
throw std::runtime_error(std::format("Failed to connect a context: {}", errno));
}
pa_context_set_state_callback(ctx, context_state_callback, NULL);
for (;;)
{
pa_context_state_t state;
state = pa_context_get_state(ctx);
if (state == PA_CONTEXT_READY)
break;
if (!PA_CONTEXT_IS_GOOD(state)) {
errno = pa_context_errno(ctx);
throw std::runtime_error(std::format("Context was not good! {}", errno));
/* Replace STDIN with the specified file if needed. */
if (argc > 1) {
int fd;
if ((fd = open(argv[1], O_RDONLY)) < 0) {
fprintf(stderr, __FILE__": open() failed: %s\n", strerror(errno));
goto finish;
}
// Wait until the context is ready
//pa_mainloop_wait(maneloop);
}
#pragma endregion
auto state = pa_context_get_state(ctx);
if (state != PA_CONTEXT_READY) {
if (dup2(fd, STDIN_FILENO) < 0) {
fprintf(stderr, __FILE__": dup2() failed: %s\n", strerror(errno));
goto finish;
}
close(fd);
}
if (!(stream = pa_stream_new(ctx, "This Dick", &ss, map)))
{
errno = pa_context_errno(ctx);
throw std::runtime_error(std::format("Failed to create a stream. Error code {}", errno));
/* 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));
goto finish;
}
//pa_stream_set_state_callback(stream, stream_state)
for (;;) {
uint8_t buf[BUFSIZE];
ssize_t r;
#if 0
pa_usec_t latency;
return 0;
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
/* Read some data */
if ((r = read(STDIN_FILENO, buf, sizeof(buf))) <= 0) {
if (r == 0) /* EOF */
break;
fprintf(stderr, __FILE__": read() failed: %s\n", strerror(errno));
goto finish;
}
/* and play it */
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;
}