diff --git a/include/ReMixer/PulseSubsystem.h b/include/ReMixer/PulseSubsystem.h index 28f12d3..f12429b 100644 --- a/include/ReMixer/PulseSubsystem.h +++ b/include/ReMixer/PulseSubsystem.h @@ -29,14 +29,30 @@ namespace ReMixer class PulseAudioSubsystem : public SoundSubsystem { std::map streams; + public: + Event<> OnReady; + Event<> OnTerminate; + Event<> OnFail; + Event<> OnStateChange; + Event OnSuccess; + Event 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; + }; } \ No newline at end of file diff --git a/include/ReMixer/ReMixer.h b/include/ReMixer/ReMixer.h index 8c98cde..1baf718 100644 --- a/include/ReMixer/ReMixer.h +++ b/include/ReMixer/ReMixer.h @@ -21,5 +21,8 @@ namespace ReMixer { class PulseAudioSubsystem; class PulseStream; + + void Init(); + void Cleanup(); } diff --git a/include/ReMixer/sound.h b/include/ReMixer/sound.h index 48e84f0..5c54b35 100644 --- a/include/ReMixer/sound.h +++ b/include/ReMixer/sound.h @@ -73,13 +73,18 @@ namespace ReMixer { void LoadPCMBuffer(std::vector 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();} diff --git a/include/ReMixer/stream.h b/include/ReMixer/stream.h index 34ea74b..e4cd61a 100644 --- a/include/ReMixer/stream.h +++ b/include/ReMixer/stream.h @@ -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); diff --git a/main.cpp b/main.cpp index 05808d1..6fcf198 100644 --- a/main.cpp +++ b/main.cpp @@ -19,10 +19,8 @@ #include #include - [[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); diff --git a/pacat_advanced.cpp b/pacat_advanced.cpp index 0c842fe..2806760 100644 --- a/pacat_advanced.cpp +++ b/pacat_advanced.cpp @@ -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); diff --git a/src/linux/PulseSubsystem.cpp b/src/linux/PulseSubsystem.cpp index 2d38416..21a199e 100644 --- a/src/linux/PulseSubsystem.cpp +++ b/src/linux/PulseSubsystem.cpp @@ -1,6 +1,6 @@ #include -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_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(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(userdata); + 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); + 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(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 (userdata); + + pa_threaded_mainloop_signal(p->pa->mainloop, 0); + } + + if (info) + { + + } +} \ No newline at end of file diff --git a/src/linux/sound.cpp b/src/linux/sound.cpp index baa7e85..cb9131a 100644 --- a/src/linux/sound.cpp +++ b/src/linux/sound.cpp @@ -103,4 +103,27 @@ void ReMixer::Sound::LoadOGGVorbisFile(const std::filesystem::path &file_name) { while ((bytes = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, ¤t_section)) > 0) { audio_data.insert(audio_data.end(), pcmout, pcmout + bytes); } -} \ No newline at end of file +} + +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(); +} diff --git a/src/linux/stream.cpp b/src/linux/stream.cpp index 1acf8c9..786e04b 100644 --- a/src/linux/stream.cpp +++ b/src/linux/stream.cpp @@ -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(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(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(userdata); pa_threaded_mainloop_signal(p, 0); } + +void ReMixer::PulseStream::OnStreamSuccess(pa_stream *s, int success, void *userdata) { + + +}