Putting the puzzle together (Sound::PlaybackLength)

This commit is contained in:
2024-09-05 16:04:22 -04:00
parent 966d5ee6fd
commit 78ee29288b
9 changed files with 203 additions and 27 deletions

View File

@@ -29,14 +29,30 @@ namespace ReMixer
class PulseAudioSubsystem : public SoundSubsystem
{
std::map<std::string, PulseStream> streams;
public:
Event<> OnReady;
Event<> OnTerminate;
Event<> OnFail;
Event<> OnStateChange;
Event<bool> OnSuccess;
Event<const pa_server_info*> OnServerInfo;
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);
/// Plays the given sound over the given audio stream.
void Play(PulseStream& stream, const Sound& sound);
bool Connected();
void ListDevices();
/// Returns the name of the PulseAudio context.
std::string ContextName() const;
void SetContextName(const std::string& ctx_name);
bool Connected() const;
std::string ServerName() const;
PulseStream CreateStream(const std::string& stream_name);
protected:
@@ -54,9 +70,22 @@ namespace ReMixer
int operation_success;
};
private:
#pragma region PulseAudio Internal Callbacks
static void OnCtxSuccess(pa_context* c, int success, void* userdata);
static void OnCtxStateChange(pa_context* c, void* userdata);
static void OnSinkDevice(pa_context* c, const pa_sink_info* info, int eol, void* userdata);
static void OnSourceDevice(pa_context* c, const pa_source_info* info, int eol, void* userdata);
static void OnListServerInfo(pa_context* c, const pa_server_info* i, void* userdata);
static void OnListSinkInfo(pa_context* c, const pa_sink_info* i, int eol, void* userdata);
static void OnStreamStateChange(pa_stream* s, void* userdata);
static void OnStreamRequest(pa_stream* s, size_t length, void* userdata);
#pragma endregion
pa_impl* pa;
bool ctx_connected = false;
std::string ctx_name;
};
}

View File

@@ -21,5 +21,8 @@ namespace ReMixer {
class PulseAudioSubsystem;
class PulseStream;
void Init();
void Cleanup();
}

View File

@@ -73,13 +73,18 @@ namespace ReMixer {
void LoadPCMBuffer(std::vector<char> buffer, uint sampleRate = 44.1_kHz, StreamMode streamMode = StreamMode::STEREO);
/// Returns the sample count of the sound.
unsigned int SampleCount() const { return audio_data.size();};
uint SampleCount() const;;
/// Returns the amount of bytes the sounds' data occupies.
uint DataSizeInBytes() const;
/// Returns the samples-per-second of this sound.
uint SampleRate() const { return sample_rate;}
uint SampleRate() const;
StreamMode GetStreamMode() const;
uint Channels() const;
float PlaybackLength();
/// Returns a pointer to the beginning of the audio data
char* ptr() { return audio_data.data();}

View File

@@ -34,7 +34,7 @@ namespace ReMixer {
/// TODO: It may not be possible to actually change these parameters after creation of the stream object.
void SetStreamDirection(const StreamDirection& direction);
void SetName(const std::string& name);
void SetSampleRate(uint sample_rate);
virtual void SetSampleRate(uint sample_rate);
void SetChannelCount(uint channel_count);
public:
@@ -59,8 +59,11 @@ namespace ReMixer {
// PulseStream(PulseAudioSubsystem* parent_system) : Stream(parent_system) { }
void SetSampleRate(uint sample_rate) override;
pa_stream* stream;
static void OnStreamSuccess(pa_stream* s, int success, void* userdata);
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);

View File

@@ -19,10 +19,8 @@
#include <ReMixer/PulseSubsystem.h>
#include <ReMixer/ReMixer.h>
[[noreturn]] int main() {
using namespace ReMixer;
using namespace std::chrono_literals;
@@ -30,20 +28,32 @@
PulseAudioSubsystem test = PulseAudioSubsystem("PulseAudio Test");
PulseStream test_stream = test.CreateStream("Test Stream");
PulseStream test_stream2 = test.CreateStream("Another Test Stream");
test.OnServerInfo += [] (const pa_server_info* info) {
std::cout << info->host_name << " @ " << info->user_name << ", "
<< info->server_name << " v" << info->server_version << ", "
<< "default sink: " << info->default_sink_name << ", "
<< "default source" << info->default_source_name << std::endl;
};
Sound test_sound = Sound::FromPCMFile("wind.raw");
Sound ogg_test_sound = Sound::FromOGGVorbisFile("wind.ogg");
PulseStream test_stream = test.CreateStream("Test Stream");
//PulseStream test_stream2 = test.CreateStream("Another Test Stream");
//Sound test_sound = Sound::FromPCMFile("output.raw");
Sound ogg_test_sound = Sound::FromOGGVorbisFile("song.ogg");
auto sample_depth = 2;
std::cout << "Sound Playback Length: " << ogg_test_sound.PlaybackLength() << " seconds." << std::endl;
std::thread playback_thread([](PulseAudioSubsystem system, PulseStream stream, Sound sound){
system.Play(stream, sound);
}, test, test_stream, ogg_test_sound);
playback_thread.join();
playback_thread.detach();
while (true) {
std::this_thread::sleep_for(25ms);
}
//test.Play(test_stream2, ogg_test_sound);

View File

@@ -316,7 +316,7 @@ int pa_system_enumerate_devices(pa_system* p)
for (;;) {
int r = pa_operation_get_state(op);
if (r == PA_OPERATION_DONE || r == PA_OPERATION_CANCELLED || r == PA_OPERATION_CANCELED)
if (r == PA_OPERATION_DONE || r == PA_OPERATION_CANCELLED)
break;
pa_threaded_mainloop_wait(p->mainloop);

View File

@@ -1,6 +1,6 @@
#include <ReMixer/PulseSubsystem.h>
void ReMixer::PulseAudioSubsystem::Play(const ReMixer::PulseStream &stream, const ReMixer::Sound &sound) {
void ReMixer::PulseAudioSubsystem::Play(ReMixer::PulseStream &stream, const ReMixer::Sound &sound) {
// Check parameters and compatibility between stream and sound.
if (stream.SampleRate() != sound.SampleRate())
@@ -8,6 +8,8 @@ void ReMixer::PulseAudioSubsystem::Play(const ReMixer::PulseStream &stream, cons
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;
stream.SetSampleRate(sound.SampleRate());
}
pa_threaded_mainloop_lock(pa->mainloop);
@@ -35,10 +37,24 @@ void ReMixer::PulseAudioSubsystem::Play(const ReMixer::PulseStream &stream, cons
pa_threaded_mainloop_unlock(pa->mainloop);
}
bool ReMixer::PulseAudioSubsystem::Connected() {
bool ReMixer::PulseAudioSubsystem::Connected() const {
return ctx_connected;
}
std::string ReMixer::PulseAudioSubsystem::ContextName() const {
return ctx_name;
}
std::string ReMixer::PulseAudioSubsystem::ServerName() const
{
return pa_context_get_server(this->pa->context);
}
void ReMixer::PulseAudioSubsystem::SetContextName(const std::string &new_context_name) {
this->ctx_name = new_context_name;
pa_context_set_name(this->pa->context, new_context_name.c_str(), OnCtxSuccess, this);
}
ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::string& stream_name) {
// Assumed defaults
// TODO: Expose to the API later
@@ -69,7 +85,7 @@ ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::strin
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);
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_VARIABLE_RATE);
// TODO: Support Playback and Recording streams
int r = pa_stream_connect_playback(new_stream.stream, dev, attr, flags, volume, NULL);
@@ -105,6 +121,8 @@ ReMixer::PulseStream ReMixer::PulseAudioSubsystem::CreateStream(const std::strin
ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_name) : SoundSubsystem()
{
ctx_name = context_name;
const char *server = NULL;
pa = pa_xnew(pa_impl, 1);
@@ -124,7 +142,7 @@ ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_nam
pa_context_flags flags = pa_context_flags::PA_CONTEXT_NOAUTOSPAWN;
pa_context_set_state_callback(pa->context, OnCtxStateChange, pa);
pa_context_set_state_callback(pa->context, OnCtxStateChange, this);
if (pa_context_connect(pa->context, server, flags, NULL) < 0)
@@ -158,25 +176,41 @@ ReMixer::PulseAudioSubsystem::PulseAudioSubsystem(const std::string &context_nam
pa_threaded_mainloop_wait(pa->mainloop);
}
pa_threaded_mainloop_unlock(pa->mainloop);
pa_context_get_server_info(pa->context, OnListServerInfo, this);
pa_context_get_sink_info_list(pa->context, OnListSinkInfo, this);
std::cout << "PulseAudio context is ready." << std::endl;
}
void ReMixer::PulseAudioSubsystem::OnCtxSuccess(pa_context* c, int success, void* userdata)
{
auto* p = static_cast<PulseAudioSubsystem *>(userdata);
if (success)
p->OnSuccess.Invoke(true);
else
p->OnSuccess.Invoke(false);
}
void ReMixer::PulseAudioSubsystem::OnCtxStateChange(pa_context *c, void *userdata) {
std::cout << "context state callback: ";
auto *p = static_cast<pa_impl *>(userdata);
auto* p = static_cast<PulseAudioSubsystem *>(userdata);
switch(pa_context_get_state(c)) {
case PA_CONTEXT_READY:
std::cout << "Context Ready" << std::endl;
pa_threaded_mainloop_signal(p->mainloop, 0);
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
break;
case PA_CONTEXT_TERMINATED:
std::cout << "Context Terminated" << std::endl;
pa_threaded_mainloop_signal(p->mainloop, 0);
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
break;
case PA_CONTEXT_FAILED:
std::cout << "Context Failed" << std::endl;
pa_threaded_mainloop_signal(p->mainloop, 0);
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
break;
case PA_CONTEXT_UNCONNECTED:
std::cout << "Context Unconnected" << std::endl;
@@ -195,3 +229,52 @@ void ReMixer::PulseAudioSubsystem::OnCtxStateChange(pa_context *c, void *userdat
break;
}
}
void ReMixer::PulseAudioSubsystem::ListDevices() {
pa_threaded_mainloop_lock(pa->mainloop);
pa_operation * op;
// List 'Sinks' aka playback devices
op = pa_context_get_sink_info_list(pa->context, OnSinkDevice, this);
for (;;)
{
int r = pa_operation_get_state(op);
if (r == PA_OPERATION_DONE || r == PA_OPERATION_CANCELLED)
break;
pa_threaded_mainloop_wait(pa->mainloop);
}
pa_operation_unref(op);
pa_threaded_mainloop_unlock(pa->mainloop);
}
void ReMixer::PulseAudioSubsystem::OnListServerInfo(pa_context *c, const pa_server_info *i, void *userdata) {
auto* p = static_cast<PulseAudioSubsystem*>(userdata);
p->OnServerInfo.Invoke(i);
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
}
void ReMixer::PulseAudioSubsystem::OnListSinkInfo(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
}
void ReMixer::PulseAudioSubsystem::OnSinkDevice(pa_context* c, const pa_sink_info* info, int eol, void* userdata)
{
if (eol != 0)
{
auto* p = static_cast<PulseAudioSubsystem*> (userdata);
pa_threaded_mainloop_signal(p->pa->mainloop, 0);
}
if (info)
{
}
}

View File

@@ -104,3 +104,26 @@ void ReMixer::Sound::LoadOGGVorbisFile(const std::filesystem::path &file_name) {
audio_data.insert(audio_data.end(), pcmout, pcmout + bytes);
}
}
uint ReMixer::Sound::Channels() const {
if (stream_mode == StreamMode::MONO) return 1;
if (stream_mode == StreamMode::STEREO) return 2;
}
ReMixer::StreamMode ReMixer::Sound::GetStreamMode() const { return stream_mode;}
uint ReMixer::Sound::SampleRate() const { return sample_rate;}
float ReMixer::Sound::PlaybackLength() {
// TODO: Factor in sample_depth properly
float sample_depth = 2;
float sample_count = (float)SampleCount();
return sample_count / (SampleRate() * Channels() * sample_depth);
}
uint ReMixer::Sound::SampleCount() const {
// TODO: Should we divide the audio buffer's real size by sample_depth?
// Consider sample_depth = 2, each 16-bit sample would hold two elements in the char buffer.
return audio_data.size();
}

View File

@@ -10,14 +10,29 @@ void ReMixer::Stream::SetName(const std::string& name)
}
void ReMixer::Stream::SetSampleRate(uint sample_rate)
{ this->sample_rate = sample_rate;}
{
this->sample_rate = sample_rate;
}
void ReMixer::Stream::SetChannelCount(uint channel_count)
{ this->channel_count = channel_count;}
void ReMixer::PulseStream::SetSampleRate(uint sampleRate)
{
//pa_threaded_mainloop_lock(this->parent-)
Stream::SetSampleRate(sampleRate);
pa_operation* op;
op = pa_stream_update_sample_rate(this->stream, sampleRate, OnStreamSuccess, this);
pa_operation_unref(op);
}
void ReMixer::PulseStream::OnStreamStateChanged(pa_stream *s, void *userdata) {
std::cout << "stream state callback: ";
//std::cout << "stream state callback: ";
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
@@ -45,7 +60,7 @@ void ReMixer::PulseStream::OnStreamStateChanged(pa_stream *s, void *userdata) {
}
void ReMixer::PulseStream::OnStreamRequest(pa_stream *s, size_t length, void *userdata) {
std::cout << "Received stream request callback: " << length << std::endl;
//std::cout << "Received stream request callback: " << length << std::endl;
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
@@ -53,8 +68,13 @@ void ReMixer::PulseStream::OnStreamRequest(pa_stream *s, size_t length, void *us
}
void ReMixer::PulseStream::OnLatencyUpdate(pa_stream *s, void *userdata) {
std::cout << "Received stream latency update callback" << std::endl;
//std::cout << "Received stream latency update callback" << std::endl;
auto *p = static_cast<pa_threaded_mainloop *>(userdata);
pa_threaded_mainloop_signal(p, 0);
}
void ReMixer::PulseStream::OnStreamSuccess(pa_stream *s, int success, void *userdata) {
}