fully functional music playback
This commit is contained in:
parent
a051a8d574
commit
5cf4b965e0
11 changed files with 161 additions and 31 deletions
|
@ -11,9 +11,10 @@ alsa_dep = dependency('alsa')
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
'waffle',
|
'waffle',
|
||||||
'./src/OpusFile.cpp',
|
'./src/player/OpusFile.cpp',
|
||||||
'./src/ALSAOutput.cpp',
|
'./src/player/ALSAOutput.cpp',
|
||||||
'./src/AppWidget.cpp',
|
'./src/player/Player.cpp',
|
||||||
|
'./src/ui/AppWidget.cpp',
|
||||||
'./src/main.cpp',
|
'./src/main.cpp',
|
||||||
dependencies : [raven_dep, opus_dep, opusfile_dep, alsa_dep]
|
dependencies : [raven_dep, opus_dep, opusfile_dep, alsa_dep]
|
||||||
)
|
)
|
17
src/main.cpp
17
src/main.cpp
|
@ -1,27 +1,24 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include "raven/Window.hpp"
|
#include "raven/Window.hpp"
|
||||||
#include "raven/Application.hpp"
|
#include "raven/Application.hpp"
|
||||||
#include "raven/Widget.hpp"
|
#include "raven/Widget.hpp"
|
||||||
#include "raven/Button.hpp"
|
#include "raven/Button.hpp"
|
||||||
#include "AppWidget.hpp"
|
#include "src/player/Player.hpp"
|
||||||
#include "src/ALSAOutput.hpp"
|
#include "ui/AppWidget.hpp"
|
||||||
#include "src/OpusFile.hpp"
|
#include "player/ALSAOutput.hpp"
|
||||||
|
#include "player/OpusFile.hpp"
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
OpusFile file;
|
Player player;
|
||||||
file.open("song");
|
|
||||||
ALSAOutput output;
|
|
||||||
output.open();
|
|
||||||
output.configure(48000, 2, 10, 100000);
|
|
||||||
output.play(&file);
|
|
||||||
|
|
||||||
Raven::Application application(true);
|
Raven::Application application(true);
|
||||||
auto window = application.add_window<Raven::Window>();
|
auto window = application.add_window<Raven::Window>();
|
||||||
|
|
||||||
window->spawn_window();
|
window->spawn_window();
|
||||||
|
|
||||||
window->set_main_widget<AppWidget>();
|
window->set_main_widget<AppWidget>(&player);
|
||||||
|
|
||||||
return application.run();
|
return application.run();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
int ALSAOutput::open() {
|
int ALSAOutput::open(std::string output_name) {
|
||||||
if (snd_pcm_open(&m_pcm, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) return -1;
|
if (snd_pcm_open(&m_pcm, output_name.c_str(), SND_PCM_STREAM_PLAYBACK, 0) < 0) return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -32,13 +32,17 @@ int ALSAOutput::configure(int sample_rate, int channels, int periods, float peri
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ALSAOutput::play(AbstractMusicFile *file) {
|
int ALSAOutput::write(int16_t *data, size_t samples) {
|
||||||
while (true) {
|
return snd_pcm_writei(m_pcm, data, samples);
|
||||||
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::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);
|
||||||
}
|
}
|
|
@ -1,16 +1,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "src/AbstractMusicFile.hpp"
|
#include "AbstractMusicFile.hpp"
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
#include <alsa/pcm.h>
|
||||||
|
|
||||||
|
|
||||||
class ALSAOutput {
|
class ALSAOutput {
|
||||||
public:
|
public:
|
||||||
ALSAOutput() {}
|
ALSAOutput() {}
|
||||||
|
~ALSAOutput();
|
||||||
|
|
||||||
int open();
|
int open(std::string output_name);
|
||||||
int configure(int sample_rate, int channels, int periods, float period_time);
|
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:
|
private:
|
||||||
snd_pcm_t *m_pcm { nullptr };
|
snd_pcm_t *m_pcm { nullptr };
|
||||||
};
|
};
|
|
@ -5,6 +5,10 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int OpusFile::open(std::string file_path) {
|
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);
|
m_file = op_open_file(file_path.c_str(), NULL);
|
||||||
if (m_file == NULL) return -1;
|
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) {
|
int OpusFile::read(int16_t *pcm, int buf_size) {
|
||||||
return op_read_stereo(m_file, pcm, buf_size);
|
return op_read_stereo(m_file, pcm, buf_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpusFile::~OpusFile() {
|
||||||
|
op_free(m_file);
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
class OpusFile : public AbstractMusicFile {
|
class OpusFile : public AbstractMusicFile {
|
||||||
public:
|
public:
|
||||||
OpusFile() {}
|
OpusFile() {}
|
||||||
|
~OpusFile();
|
||||||
|
|
||||||
int open(std::string file_path);
|
int open(std::string file_path);
|
||||||
int read(int16_t *pcm, int buf_size);
|
int read(int16_t *pcm, int buf_size);
|
68
src/player/Player.cpp
Normal file
68
src/player/Player.cpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#include "Player.hpp"
|
||||||
|
#include "ALSAOutput.hpp"
|
||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <exception>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
void Player::start_playback_thread() {
|
||||||
|
m_playback_thread = new std::thread([this]() {
|
||||||
|
playback_loop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::set_file(std::shared_ptr<AbstractMusicFile> file) {
|
||||||
|
std::lock_guard<std::mutex> guard(m_lock);
|
||||||
|
m_file = file;
|
||||||
|
if (m_state == State::NoThreadUntilFile) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> guard(m_lock);
|
||||||
|
samples_read = m_file->read(pcm, 176400);
|
||||||
|
if (samples_read < 1) {
|
||||||
|
m_state = State::NoThreadUntilFile;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
38
src/player/Player.hpp
Normal file
38
src/player/Player.hpp
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AbstractMusicFile.hpp"
|
||||||
|
#include "ALSAOutput.hpp"
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
public:
|
||||||
|
Player();
|
||||||
|
~Player();
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
NoThreadUntilFile,
|
||||||
|
Paused,
|
||||||
|
Playing
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<AbstractMusicFile> file() { return m_file; }
|
||||||
|
void set_file(std::shared_ptr<AbstractMusicFile> 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<AbstractMusicFile> m_file { nullptr };
|
||||||
|
/**/
|
||||||
|
|
||||||
|
std::mutex m_alsa_lock;
|
||||||
|
/**/
|
||||||
|
ALSAOutput *m_alsa_output { nullptr };
|
||||||
|
/**/
|
||||||
|
};
|
|
@ -9,14 +9,21 @@
|
||||||
#include "raven/BoxLayout.hpp"
|
#include "raven/BoxLayout.hpp"
|
||||||
#include "raven/Label.hpp"
|
#include "raven/Label.hpp"
|
||||||
#include "raven/DocumentLayout.hpp"
|
#include "raven/DocumentLayout.hpp"
|
||||||
|
#include "src/player/OpusFile.hpp"
|
||||||
|
#include "src/player/Player.hpp"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
static void populate_song_list(std::filesystem::path songs_directory, std::shared_ptr<Raven::Widget> song_list_widget) {
|
static void populate_song_list(std::filesystem::path songs_directory, std::shared_ptr<Raven::Widget> song_list_widget, Player *player) {
|
||||||
song_list_widget->clear_children();
|
song_list_widget->clear_children();
|
||||||
for (const auto &entry : std::filesystem::directory_iterator(songs_directory)) {
|
for (const auto &entry : std::filesystem::directory_iterator(songs_directory)) {
|
||||||
std::string filename = entry.path().filename();
|
std::string filename = entry.path().filename();
|
||||||
std::string path_string = entry.path().string();
|
std::string path_string = entry.path().string();
|
||||||
auto button = song_list_widget->add<Raven::Button>(filename);
|
auto button = song_list_widget->add<Raven::Button>(filename);
|
||||||
|
button->on_click = [player, path_string]() {
|
||||||
|
auto file = std::make_shared<OpusFile>();
|
||||||
|
file->open(path_string);
|
||||||
|
player->set_file(file);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +45,7 @@ void AppWidget::on_init() {
|
||||||
layout->set_inherit_secondary_dimension(true);
|
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();
|
reflow();
|
||||||
set_did_init(true);
|
set_did_init(true);
|
|
@ -3,14 +3,17 @@
|
||||||
#include "raven/ScrollContainer.hpp"
|
#include "raven/ScrollContainer.hpp"
|
||||||
#include "raven/Widget.hpp"
|
#include "raven/Widget.hpp"
|
||||||
#include "raven/TextInput.hpp"
|
#include "raven/TextInput.hpp"
|
||||||
|
#include "src/player/Player.hpp"
|
||||||
|
|
||||||
class AppWidget : public Raven::Widget {
|
class AppWidget : public Raven::Widget {
|
||||||
public:
|
public:
|
||||||
AppWidget()
|
AppWidget(Player *player)
|
||||||
: Raven::Widget() {}
|
: Raven::Widget()
|
||||||
|
, m_player(player) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void on_init() override;
|
void on_init() override;
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Raven::ScrollContainer> m_song_list;
|
std::shared_ptr<Raven::ScrollContainer> m_song_list;
|
||||||
|
Player *m_player;
|
||||||
};
|
};
|
Loading…
Reference in a new issue