From 5cf4b965e032000e6e6393cecf416d6d3328dfea Mon Sep 17 00:00:00 2001 From: hippoz <10706925-hippoz@users.noreply.gitlab.com> Date: Mon, 7 Nov 2022 18:52:23 +0200 Subject: [PATCH] fully functional music playback --- meson.build | 7 +-- src/main.cpp | 17 +++---- src/{ => player}/ALSAOutput.cpp | 26 +++++----- src/{ => player}/ALSAOutput.hpp | 9 ++-- src/{ => player}/AbstractMusicFile.hpp | 0 src/{ => player}/OpusFile.cpp | 8 +++ src/{ => player}/OpusFile.hpp | 1 + src/player/Player.cpp | 68 ++++++++++++++++++++++++++ src/player/Player.hpp | 38 ++++++++++++++ src/{ => ui}/AppWidget.cpp | 11 ++++- src/{ => ui}/AppWidget.hpp | 7 ++- 11 files changed, 161 insertions(+), 31 deletions(-) rename src/{ => player}/ALSAOutput.cpp (67%) rename src/{ => player}/ALSAOutput.hpp (55%) rename src/{ => player}/AbstractMusicFile.hpp (100%) rename src/{ => player}/OpusFile.cpp (77%) rename src/{ => player}/OpusFile.hpp (94%) create mode 100644 src/player/Player.cpp create mode 100644 src/player/Player.hpp rename src/{ => ui}/AppWidget.cpp (79%) rename src/{ => ui}/AppWidget.hpp (65%) diff --git a/meson.build b/meson.build index 4b58c04..a2d389e 100644 --- a/meson.build +++ b/meson.build @@ -11,9 +11,10 @@ alsa_dep = dependency('alsa') executable( 'waffle', - './src/OpusFile.cpp', - './src/ALSAOutput.cpp', - './src/AppWidget.cpp', + './src/player/OpusFile.cpp', + './src/player/ALSAOutput.cpp', + './src/player/Player.cpp', + './src/ui/AppWidget.cpp', './src/main.cpp', dependencies : [raven_dep, opus_dep, opusfile_dep, alsa_dep] ) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0834972..a71b582 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,27 +1,24 @@ #include +#include #include "raven/Window.hpp" #include "raven/Application.hpp" #include "raven/Widget.hpp" #include "raven/Button.hpp" -#include "AppWidget.hpp" -#include "src/ALSAOutput.hpp" -#include "src/OpusFile.hpp" +#include "src/player/Player.hpp" +#include "ui/AppWidget.hpp" +#include "player/ALSAOutput.hpp" +#include "player/OpusFile.hpp" int main() { - OpusFile file; - file.open("song"); - ALSAOutput output; - output.open(); - output.configure(48000, 2, 10, 100000); - output.play(&file); + Player player; Raven::Application application(true); auto window = application.add_window(); window->spawn_window(); - window->set_main_widget(); + window->set_main_widget(&player); return application.run(); } diff --git a/src/ALSAOutput.cpp b/src/player/ALSAOutput.cpp similarity index 67% rename from src/ALSAOutput.cpp rename to src/player/ALSAOutput.cpp index fc4842b..ff49519 100644 --- a/src/ALSAOutput.cpp +++ b/src/player/ALSAOutput.cpp @@ -5,8 +5,8 @@ #include -int ALSAOutput::open() { - if (snd_pcm_open(&m_pcm, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) return -1; +int ALSAOutput::open(std::string output_name) { + if (snd_pcm_open(&m_pcm, output_name.c_str(), SND_PCM_STREAM_PLAYBACK, 0) < 0) return -1; return 0; } @@ -32,13 +32,17 @@ int ALSAOutput::configure(int sample_rate, int channels, int periods, float peri return 0; } -int ALSAOutput::play(AbstractMusicFile *file) { - while (true) { - int16_t pcm[176400]; - memset(pcm, 0, sizeof(pcm)); - - size_t n_samples = file->read(pcm, 176400); - - snd_pcm_writei(m_pcm, pcm, n_samples); - } +int ALSAOutput::write(int16_t *data, size_t samples) { + return snd_pcm_writei(m_pcm, data, samples); +} + +int ALSAOutput::drop() { + snd_pcm_drop(m_pcm); + snd_pcm_prepare(m_pcm); + return 0; +} + +ALSAOutput::~ALSAOutput() { + snd_pcm_drain(m_pcm); + snd_pcm_close(m_pcm); } diff --git a/src/ALSAOutput.hpp b/src/player/ALSAOutput.hpp similarity index 55% rename from src/ALSAOutput.hpp rename to src/player/ALSAOutput.hpp index cb1be31..7405b0d 100644 --- a/src/ALSAOutput.hpp +++ b/src/player/ALSAOutput.hpp @@ -1,16 +1,19 @@ #pragma once -#include "src/AbstractMusicFile.hpp" +#include "AbstractMusicFile.hpp" #include +#include class ALSAOutput { public: ALSAOutput() {} + ~ALSAOutput(); - int open(); + int open(std::string output_name); int configure(int sample_rate, int channels, int periods, float period_time); - int play(AbstractMusicFile *file); + int write(int16_t *data, size_t samples); + int drop(); private: snd_pcm_t *m_pcm { nullptr }; }; diff --git a/src/AbstractMusicFile.hpp b/src/player/AbstractMusicFile.hpp similarity index 100% rename from src/AbstractMusicFile.hpp rename to src/player/AbstractMusicFile.hpp diff --git a/src/OpusFile.cpp b/src/player/OpusFile.cpp similarity index 77% rename from src/OpusFile.cpp rename to src/player/OpusFile.cpp index e02775e..fd3528a 100644 --- a/src/OpusFile.cpp +++ b/src/player/OpusFile.cpp @@ -5,6 +5,10 @@ #include int OpusFile::open(std::string file_path) { + if (m_file != NULL) { + op_free(m_file); + } + m_file = op_open_file(file_path.c_str(), NULL); if (m_file == NULL) return -1; @@ -14,3 +18,7 @@ int OpusFile::open(std::string file_path) { int OpusFile::read(int16_t *pcm, int buf_size) { return op_read_stereo(m_file, pcm, buf_size); } + +OpusFile::~OpusFile() { + op_free(m_file); +} diff --git a/src/OpusFile.hpp b/src/player/OpusFile.hpp similarity index 94% rename from src/OpusFile.hpp rename to src/player/OpusFile.hpp index d57d77f..47d9daf 100644 --- a/src/OpusFile.hpp +++ b/src/player/OpusFile.hpp @@ -8,6 +8,7 @@ class OpusFile : public AbstractMusicFile { public: OpusFile() {} + ~OpusFile(); int open(std::string file_path); int read(int16_t *pcm, int buf_size); diff --git a/src/player/Player.cpp b/src/player/Player.cpp new file mode 100644 index 0000000..34f459f --- /dev/null +++ b/src/player/Player.cpp @@ -0,0 +1,68 @@ +#include "Player.hpp" +#include "ALSAOutput.hpp" +#include +#include +#include +#include +#include +#include + +void Player::start_playback_thread() { + m_playback_thread = new std::thread([this]() { + playback_loop(); + }); +} + +void Player::set_file(std::shared_ptr file) { + std::lock_guard guard(m_lock); + m_file = file; + if (m_state == State::NoThreadUntilFile) { + { + std::lock_guard alsa_guard(m_alsa_lock); + m_alsa_output->drop(); + } + + start_playback_thread(); + m_state = State::Playing; + } else if (m_state == State::Playing) { + std::lock_guard alsa_guard(m_alsa_lock); + m_alsa_output->drop(); + } +} + +void Player::playback_loop() { + while (true) { + int16_t pcm[176400]; + size_t samples_read = 0; + + { + std::lock_guard guard(m_lock); + samples_read = m_file->read(pcm, 176400); + if (samples_read < 1) { + m_state = State::NoThreadUntilFile; + return; + } + } + + { + std::lock_guard guard(m_alsa_lock); + m_alsa_output->write(pcm, samples_read); + } + } +} + +Player::Player() { + m_alsa_output = new ALSAOutput(); + m_alsa_output->open("pipewire"); + m_alsa_output->configure(48000, 2, 1, 100000); +} + +Player::~Player() { + if (m_alsa_output) { + delete m_alsa_output; + } + if (m_playback_thread) { + delete m_playback_thread; + } +} + diff --git a/src/player/Player.hpp b/src/player/Player.hpp new file mode 100644 index 0000000..9f234ed --- /dev/null +++ b/src/player/Player.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "AbstractMusicFile.hpp" +#include "ALSAOutput.hpp" +#include +#include +#include + + +class Player { +public: + Player(); + ~Player(); + + enum class State { + NoThreadUntilFile, + Paused, + Playing + }; + + std::shared_ptr file() { return m_file; } + void set_file(std::shared_ptr file); +private: + void playback_loop(); + void start_playback_thread(); + + std::mutex m_lock; + /**/ + std::thread *m_playback_thread { nullptr }; + State m_state { State::NoThreadUntilFile }; + std::shared_ptr m_file { nullptr }; + /**/ + + std::mutex m_alsa_lock; + /**/ + ALSAOutput *m_alsa_output { nullptr }; + /**/ +}; diff --git a/src/AppWidget.cpp b/src/ui/AppWidget.cpp similarity index 79% rename from src/AppWidget.cpp rename to src/ui/AppWidget.cpp index bd643c3..2617214 100644 --- a/src/AppWidget.cpp +++ b/src/ui/AppWidget.cpp @@ -9,14 +9,21 @@ #include "raven/BoxLayout.hpp" #include "raven/Label.hpp" #include "raven/DocumentLayout.hpp" +#include "src/player/OpusFile.hpp" +#include "src/player/Player.hpp" #include -static void populate_song_list(std::filesystem::path songs_directory, std::shared_ptr song_list_widget) { +static void populate_song_list(std::filesystem::path songs_directory, std::shared_ptr song_list_widget, Player *player) { song_list_widget->clear_children(); for (const auto &entry : std::filesystem::directory_iterator(songs_directory)) { std::string filename = entry.path().filename(); std::string path_string = entry.path().string(); auto button = song_list_widget->add(filename); + button->on_click = [player, path_string]() { + auto file = std::make_shared(); + file->open(path_string); + player->set_file(file); + }; } } @@ -38,7 +45,7 @@ void AppWidget::on_init() { layout->set_inherit_secondary_dimension(true); } - populate_song_list("/home/hippoz/music", m_song_list->target()); + populate_song_list("/home/hippoz/music", m_song_list->target(), m_player); reflow(); set_did_init(true); diff --git a/src/AppWidget.hpp b/src/ui/AppWidget.hpp similarity index 65% rename from src/AppWidget.hpp rename to src/ui/AppWidget.hpp index aa20984..eeeb689 100644 --- a/src/AppWidget.hpp +++ b/src/ui/AppWidget.hpp @@ -3,14 +3,17 @@ #include "raven/ScrollContainer.hpp" #include "raven/Widget.hpp" #include "raven/TextInput.hpp" +#include "src/player/Player.hpp" class AppWidget : public Raven::Widget { public: - AppWidget() - : Raven::Widget() {} + AppWidget(Player *player) + : Raven::Widget() + , m_player(player) {} protected: void on_init() override; private: std::shared_ptr m_song_list; + Player *m_player; };