More scoping!!!

This commit is contained in:
2024-08-29 21:47:03 -04:00
parent 9d5c7f0590
commit 3758e9bc03
7 changed files with 297 additions and 260 deletions

View File

@@ -1,3 +1,13 @@
/// ReMixer
/// A Public Domain C++ Audio Playback Library
/// By william @ RedactedSoftware. Thanks to Dawsh & Maxine.
/// (c) 2024 Redacted Software
/// This work is explicitly dedicated to the public domain, for the hopeful betterment of the software industry.
/// @file PulseSubsystem.h
/// @desc A PulseAudio implementation of the AudioSubsystem class.
/// @edit 2024-08-29
#include <pulse/pulseaudio.h>
#include <pulse/thread-mainloop.h>
#include <iostream>
@@ -18,6 +28,19 @@ namespace ReMixer
/// A PulseAudio implementation of the SoundSubsystem.
class PulseAudioSubsystem : public SoundSubsystem
{
std::map<std::string, PulseStream> streams;
public:
/// Constructs a new PulseAudio context with the given name.
/// @param context_name The context name. All streams created with this context will be grouped together by this name.
PulseAudioSubsystem(const std::string& context_name);
void Play(const PulseStream& stream, const Sound& sound);
bool Connected();
PulseStream CreateStream(const std::string& stream_name);
protected:
private:
struct pa_impl
{
pa_threaded_mainloop* mainloop;
@@ -30,201 +53,8 @@ namespace ReMixer
int operation_success;
};
std::map<std::string, PulseStream> streams;
public:
/// Constructs a new PulseAudio context with the given name.
/// @param name The context name. All streams created with this context will be grouped together by this name.
PulseAudioSubsystem(const std::string& context_name) : SoundSubsystem()
{
const char *server = NULL;
pa = pa_xnew(pa_impl, 1);
int error = PA_ERR_INTERNAL;
if (!(pa->mainloop = pa_threaded_mainloop_new()))
{
throw std::runtime_error("Failed to create a PulseAudio mainloop.");
}
pa->api = pa_threaded_mainloop_get_api(pa->mainloop);
if (!(pa->context = pa_context_new(pa->api, context_name.c_str())))
{
throw std::runtime_error("Failed to create a PulseAudio context.");
}
pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN;
pa_context_set_state_callback(pa->context, OnCtxStateChange, pa);
if (pa_context_connect(pa->context, server, flags, NULL) < 0)
{
error = pa_context_errno(pa->context);
throw std::runtime_error(std::format("Failed to connect to context {}", error));
} else{
ctx_connected = true;
}
pa_threaded_mainloop_lock(pa->mainloop);
if (pa_threaded_mainloop_start(pa->mainloop) < 0) {
throw std::runtime_error("Failed to start thread mainloop.");
}
for (;;) {
pa_context_state_t state;
state = pa_context_get_state(pa->context);
if (state == PA_CONTEXT_READY)
break;
if (!PA_CONTEXT_IS_GOOD(state)) {
error = pa_context_errno(pa->context);
throw std::runtime_error(std::format("Context was not good: {}", error));
}
// Wait until the context is ready.
pa_threaded_mainloop_wait(pa->mainloop);
}
pa_threaded_mainloop_unlock(pa->mainloop);
std::cout << "PulseAudio context is ready." << std::endl;
}
void Play(const PulseStream& stream, const Sound& sound)
{
pa_threaded_mainloop_lock(pa->mainloop);
size_t length = sound.DataSize();
const void* data = sound.ptr();
while(length > 0)
{
size_t l;
int r;
while(!(l = pa_stream_writable_size(stream.stream))) {
pa_threaded_mainloop_wait(pa->mainloop);
}
if (l > length)
l = length;
r = pa_stream_write(stream.stream, data, l, nullptr, 0LL, PA_SEEK_RELATIVE);
data = (const char*) data + l;
length -= l;
}
pa_threaded_mainloop_unlock(pa->mainloop);
}
bool Connected()
{
return ctx_connected;
}
PulseStream CreateStream(const std::string& stream_name)
{
// Assumed defaults
// TODO: Expose to the API later
static const pa_sample_spec ss = {
.format = PA_SAMPLE_S16LE,
.rate = 44100,
.channels = 2
};
const pa_cvolume* volume = NULL;
pa_channel_map* map = NULL;
const char* dev = NULL;
pa_buffer_attr* attr = NULL;
PulseStream new_stream = PulseStream();
pa_threaded_mainloop_lock(pa->mainloop);
if (!(new_stream.stream = pa_stream_new(pa->context, stream_name.c_str(), &ss, map)))
{
throw std::runtime_error("Failed to create a new PulseAudio stream.");
}
pa_stream_set_state_callback(new_stream.stream, PulseStream::OnStreamStateChanged, pa->mainloop);
pa_stream_set_read_callback(new_stream.stream, PulseStream::OnStreamRequest, pa->mainloop);
pa_stream_set_write_callback(new_stream.stream, PulseStream::OnStreamRequest, pa->mainloop);
pa_stream_set_latency_update_callback(new_stream.stream, PulseStream::OnLatencyUpdate, pa->mainloop);
pa_stream_flags flags = static_cast<pa_stream_flags>(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY |
PA_STREAM_AUTO_TIMING_UPDATE);
// TODO: Support Playback and Recording streams
int r = pa_stream_connect_playback(new_stream.stream, dev, attr, flags, volume, NULL);
if (r < 0)
{
int error = pa_context_errno(pa->context);
throw std::runtime_error(std::format("Failed to connect the stream: {}", error));
}
for (;;)
{
pa_stream_state_t state;
state = pa_stream_get_state(new_stream.stream);
if (state == PA_STREAM_READY)
break;
if (!PA_STREAM_IS_GOOD(state))
{
int error = pa_context_errno(pa->context);
throw std::runtime_error(std::format("Stream state is bad: {}", error));
}
pa_threaded_mainloop_wait(pa->mainloop);
}
pa_threaded_mainloop_unlock(pa->mainloop);
streams.insert({stream_name, new_stream});
return new_stream;
}
protected:
private:
static void OnCtxStateChange(pa_context* c, void* userdata)
{
std::cout << "context state callback: ";
auto *p = static_cast<pa_impl *>(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 OnCtxStateChange(pa_context* c, void* userdata);
pa_impl* pa;
bool ctx_connected = false;

View File

@@ -57,10 +57,7 @@ namespace ReMixer
};
class Decibels
{
};
class Decibels { };
enum class SoundApi
{
@@ -84,6 +81,11 @@ namespace ReMixer
class SoundSubsystem
{
public:
SoundSubsystem()
{
}
std::vector<AudioDevice> GetAudioDevices();
std::vector<PlaybackDevice> GetPlaybackDevices();
std::vector<RecordingDevice> GetRecordingDevices();

View File

@@ -1,3 +1,13 @@
/// ReMixer
/// A Public Domain C++ Audio Playback Library
/// By william @ RedactedSoftware. Thanks to Dawsh & Maxine.
/// (c) 2024 Redacted Software
/// This work is explicitly dedicated to the public domain, for the hopeful betterment of the software industry.
/// @file sound.h
/// @desc This class contains and manipulates sound data.
/// @edit 2024-08-29
#pragma once
#include <vector>
#include <pulse/pulseaudio.h>
@@ -15,6 +25,7 @@ namespace ReMixer {
/// The sound class contains a raw audio data buffer in Pulse-Code Modulated form.
class Sound {
public:
/// The default constructor for a Sound object is intentionally left undefined.
/// It is intended that you use the static constructors below to load Sound objects.
Sound() = default;
@@ -73,11 +84,6 @@ namespace ReMixer {
char* ptr() { return audio_data.data();}
[[nodiscard]] const char* ptr() const { return audio_data.data();}
public:
private:
uint sample_rate;
std::vector<char> audio_data;

View File

@@ -16,23 +16,24 @@
#include <pulse/thread-mainloop.h>
#include <iostream>
#include "sound.h"
#include "SoundSubsystem.h"
#include <ReMixer/ReMixer.h>
namespace ReMixer
{
namespace ReMixer {
class Stream {
private:
std::string name;
std::string parent_name;
unsigned int channel_count;
unsigned int sample_rate;
StreamDirection dir;
std::vector<char> buffer;
public:
Stream() = default;
Stream(SoundSubsystem* parent_system);
uint SampleRate() const { return sample_rate;}
unsigned int sample_rate;
size_t bufferSize;
uint16_t handle;
//Stream(const std::string& application_name, const std::string& stream_name, SampleRate sample_rate, StreamMode mode);
@@ -41,55 +42,15 @@ namespace ReMixer
class PulseStream : public Stream
{
public:
PulseStream() = default;
// PulseStream(PulseAudioSubsystem* parent_system) : Stream(parent_system) { }
pa_stream* stream;
static void OnStreamStateChanged(pa_stream* s, void* userdata)
{
std::cout << "stream state callback: ";
auto *p = static_cast<pa_threaded_mainloop *>(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, 0);
break;
case PA_STREAM_FAILED:
std::cout << "Stream Failed" << std::endl;
pa_threaded_mainloop_signal(p, 0);
break;
case PA_STREAM_TERMINATED:
std::cout << "Stream Terminated" << std::endl;
pa_threaded_mainloop_signal(p, 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 OnStreamRequest(pa_stream* s, size_t length, void* userdata)
{
std::cout << "Received stream request callback: " << length << std::endl;
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
pa_threaded_mainloop_signal(p, 0);
}
static void OnLatencyUpdate(pa_stream* s, void* userdata)
{
std::cout << "Received stream latency update callback" << std::endl;
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
pa_threaded_mainloop_signal(p, 0);
}
static void OnStreamStateChanged(pa_stream* s, void* userdata);
static void OnStreamRequest(pa_stream* s, size_t length, void* userdata);
static void OnLatencyUpdate(pa_stream* s, void* userdata);
protected:
private:

View File

@@ -21,12 +21,14 @@
[[noreturn]] int main() {
using namespace ReMixer;
using namespace std::chrono_literals;
uint measurement = 44.1_kHz;
PulseAudioSubsystem test = PulseAudioSubsystem("PulseAudio Test");
PulseStream test_stream = test.CreateStream("Test Stream");
PulseStream test_stream2 = test.CreateStream("Another Test Stream");

View File

@@ -1 +1,195 @@
#include <ReMixer/PulseSubsystem.h>
#include <ReMixer/PulseSubsystem.h>
void ReMixer::PulseAudioSubsystem::Play(const ReMixer::PulseStream &stream, const ReMixer::Sound &sound) {
if (stream.SampleRate() != sound.SampleRate())
{
std::cerr << "There is an incongruency in sample rate between the stream and the sound file. "
<< std::format("Stream: {}hz, meanwhile sound: {}hz", stream.SampleRate(), sound.SampleRate())
<< std::endl;
}
pa_threaded_mainloop_lock(pa->mainloop);
size_t length = sound.SampleCount();
const void* data = sound.ptr();
while(length > 0)
{
size_t l;
int r;
while(!(l = pa_stream_writable_size(stream.stream))) {
pa_threaded_mainloop_wait(pa->mainloop);
}
if (l > length)
l = length;
r = pa_stream_write(stream.stream, data, l, nullptr, 0LL, PA_SEEK_RELATIVE);
data = (const char*) data + l;
length -= l;
}
pa_threaded_mainloop_unlock(pa->mainloop);
}
bool ReMixer::PulseAudioSubsystem::Connected() {
return ctx_connected;
}
ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::string &stream_name) {
// Assumed defaults
// TODO: Expose to the API later
static const pa_sample_spec ss = {
.format = PA_SAMPLE_S16LE,
.rate = 44100,
.channels = 2
};
const pa_cvolume* volume = NULL;
pa_channel_map* map = NULL;
const char* dev = NULL;
pa_buffer_attr* attr = NULL;
PulseStream new_stream = PulseStream();
new_stream.sample_rate = 44.1_kHz;
pa_threaded_mainloop_lock(pa->mainloop);
if (!(new_stream.stream = pa_stream_new(pa->context, stream_name.c_str(), &ss, map)))
{
throw std::runtime_error("Failed to create a new PulseAudio stream.");
}
pa_stream_set_state_callback(new_stream.stream, PulseStream::OnStreamStateChanged, pa->mainloop);
pa_stream_set_read_callback(new_stream.stream, PulseStream::OnStreamRequest, pa->mainloop);
pa_stream_set_write_callback(new_stream.stream, PulseStream::OnStreamRequest, pa->mainloop);
pa_stream_set_latency_update_callback(new_stream.stream, PulseStream::OnLatencyUpdate, pa->mainloop);
pa_stream_flags flags = static_cast<pa_stream_flags>(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY |
PA_STREAM_AUTO_TIMING_UPDATE);
// TODO: Support Playback and Recording streams
int r = pa_stream_connect_playback(new_stream.stream, dev, attr, flags, volume, NULL);
if (r < 0)
{
int error = pa_context_errno(pa->context);
throw std::runtime_error(std::format("Failed to connect the stream: {}", error));
}
for (;;)
{
pa_stream_state_t state;
state = pa_stream_get_state(new_stream.stream);
if (state == PA_STREAM_READY)
break;
if (!PA_STREAM_IS_GOOD(state))
{
int error = pa_context_errno(pa->context);
throw std::runtime_error(std::format("Stream state is bad: {}", error));
}
pa_threaded_mainloop_wait(pa->mainloop);
}
pa_threaded_mainloop_unlock(pa->mainloop);
streams.insert({stream_name, new_stream});
return new_stream;
}
ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_name) : SoundSubsystem()
{
const char *server = NULL;
pa = pa_xnew(pa_impl, 1);
int error = PA_ERR_INTERNAL;
if (!(pa->mainloop = pa_threaded_mainloop_new()))
{
throw std::runtime_error("Failed to create a PulseAudio mainloop.");
}
pa->api = pa_threaded_mainloop_get_api(pa->mainloop);
if (!(pa->context = pa_context_new(pa->api, context_name.c_str())))
{
throw std::runtime_error("Failed to create a PulseAudio context.");
}
pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN;
pa_context_set_state_callback(pa->context, OnCtxStateChange, pa);
if (pa_context_connect(pa->context, server, flags, NULL) < 0)
{
error = pa_context_errno(pa->context);
throw std::runtime_error(std::format("Failed to connect to context {}", error));
} else{
ctx_connected = true;
}
pa_threaded_mainloop_lock(pa->mainloop);
if (pa_threaded_mainloop_start(pa->mainloop) < 0) {
throw std::runtime_error("Failed to start thread mainloop.");
}
for (;;) {
pa_context_state_t state;
state = pa_context_get_state(pa->context);
if (state == PA_CONTEXT_READY)
break;
if (!PA_CONTEXT_IS_GOOD(state)) {
error = pa_context_errno(pa->context);
throw std::runtime_error(std::format("Context was not good: {}", error));
}
// Wait until the context is ready.
pa_threaded_mainloop_wait(pa->mainloop);
}
pa_threaded_mainloop_unlock(pa->mainloop);
std::cout << "PulseAudio context is ready." << std::endl;
}
void ReMixer::PulseAudioSubsystem::OnCtxStateChange(pa_context *c, void *userdata) {
std::cout << "context state callback: ";
auto *p = static_cast<pa_impl *>(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;
}
}

View File

@@ -50,4 +50,46 @@ void Stream::erase() {
if (this == streamList[i])
streamList.erase(streamList.begin() + i);
}
*/
*/
void ReMixer::PulseStream::OnStreamStateChanged(pa_stream *s, void *userdata) {
std::cout << "stream state callback: ";
auto *p = static_cast<pa_threaded_mainloop *>(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, 0);
break;
case PA_STREAM_FAILED:
std::cout << "Stream Failed" << std::endl;
pa_threaded_mainloop_signal(p, 0);
break;
case PA_STREAM_TERMINATED:
std::cout << "Stream Terminated" << std::endl;
pa_threaded_mainloop_signal(p, 0);
break;
case PA_STREAM_UNCONNECTED:
std::cout << "Stream Unconnected" << std::endl;
break;
case PA_STREAM_CREATING:
std::cout << "Stream Creating" << std::endl;
break;
}
}
void ReMixer::PulseStream::OnStreamRequest(pa_stream *s, size_t length, void *userdata) {
std::cout << "Received stream request callback: " << length << std::endl;
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
pa_threaded_mainloop_signal(p, 0);
}
void ReMixer::PulseStream::OnLatencyUpdate(pa_stream *s, void *userdata) {
std::cout << "Received stream latency update callback" << std::endl;
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
pa_threaded_mainloop_signal(p, 0);
}