SoundPlayer: Handle any input file sample rate

This commit addresses two issues:
1. If you play a 96 KHz Wave file, the slider position is incorrect,
   because it is assumed all files are 44.1 KHz.
2. For high-bitrate files, there are audio dropouts due to not
   buffering enough audio data.

Issue 1 is addressed by scaling the number of played samples by the
ratio between the source and destination sample rates.

Issue 2 is addressed by buffering a certain number of milliseconds
worth of audio data (instead of a fixed number of bytes).
This makes the the buffer size independent of the source sample rate.

Some of the code is redesigned to be simpler. The code that did the
book-keeping of which buffers need to be loaded and which have been
already played has been removed. Instead, we enqueue a new buffer based
on a low watermark of samples remaining in the audio server queue.

Other small fixes include:
1. Disable the stop button when playback is finished.
2. Remove hard-coded instances of 44100.
3. Update the GUI every 50 ms (was 100), which improves visualizations.
This commit is contained in:
Nick Miller 2021-06-19 13:17:04 -07:00 committed by Ali Mohammad Pur
parent 34a3d08e65
commit 9a2c80c791
Notes: sideshowbarker 2024-07-18 11:57:57 +09:00
7 changed files with 53 additions and 79 deletions

View file

@ -9,7 +9,7 @@
PlaybackManager::PlaybackManager(NonnullRefPtr<Audio::ClientConnection> connection)
: m_connection(connection)
{
m_timer = Core::Timer::construct(100, [&]() {
m_timer = Core::Timer::construct(PlaybackManager::update_rate_ms, [&]() {
if (!m_loader)
return;
next_buffer();
@ -27,8 +27,10 @@ void PlaybackManager::set_loader(NonnullRefPtr<Audio::Loader>&& loader)
m_loader = loader;
if (m_loader) {
m_total_length = m_loader->total_samples() / static_cast<float>(m_loader->sample_rate());
m_device_samples_per_buffer = PlaybackManager::buffer_size_ms / 1000.0f * m_device_sample_rate;
u32 source_samples_per_buffer = PlaybackManager::buffer_size_ms / 1000.0f * m_loader->sample_rate();
m_source_buffer_size_bytes = source_samples_per_buffer * m_loader->num_channels() * m_loader->bits_per_sample() / 8;
m_timer->start();
load_next_buffer();
} else {
m_timer->stop();
}
@ -38,11 +40,8 @@ void PlaybackManager::stop()
{
set_paused(true);
m_connection->clear_buffer(true);
m_buffers.clear();
m_last_seek = 0;
m_next_buffer = nullptr;
m_current_buffer = nullptr;
m_next_ptr = 0;
if (m_loader)
m_loader->reset();
@ -68,10 +67,7 @@ void PlaybackManager::seek(const int position)
set_paused(true);
m_connection->clear_buffer(true);
m_next_buffer = nullptr;
m_current_buffer = nullptr;
m_next_ptr = 0;
m_buffers.clear();
m_loader->seek(position);
if (!paused_state)
@ -83,51 +79,8 @@ void PlaybackManager::pause()
set_paused(true);
}
void PlaybackManager::remove_dead_buffers()
{
int id = m_connection->get_playing_buffer();
int current_id = -1;
if (m_current_buffer)
current_id = m_current_buffer->id();
if (id >= 0 && id != current_id) {
while (!m_buffers.is_empty()) {
--m_next_ptr;
auto buffer = m_buffers.take_first();
if (buffer->id() == id) {
m_current_buffer = buffer;
break;
}
}
}
}
void PlaybackManager::load_next_buffer()
{
if (m_buffers.size() < 10) {
for (int i = 0; i < 20 && m_loader->loaded_samples() < m_loader->total_samples(); i++) {
auto buffer = m_loader->get_more_samples(PLAYBACK_MANAGER_BUFFER_SIZE);
if (buffer) {
m_buffers.append(buffer);
}
}
}
if (m_next_ptr < m_buffers.size()) {
m_next_buffer = m_buffers.at(m_next_ptr++);
if (on_load_sample_buffer)
on_load_sample_buffer(*m_next_buffer);
} else {
m_next_buffer = nullptr;
}
}
void PlaybackManager::set_paused(bool paused)
{
if (!m_next_buffer && m_loader)
load_next_buffer();
m_paused = paused;
m_connection->set_paused(paused);
}
@ -146,22 +99,24 @@ void PlaybackManager::next_buffer()
{
if (on_update)
on_update();
if (m_paused)
return;
remove_dead_buffers();
if (!m_next_buffer) {
if (!m_connection->get_remaining_samples() && !m_paused) {
stop();
if (on_finished_playing)
on_finished_playing();
}
u32 audio_server_remaining_samples = m_connection->get_remaining_samples();
bool all_samples_loaded = (m_loader->loaded_samples() >= m_loader->total_samples());
bool audio_server_done = (audio_server_remaining_samples == 0);
if (all_samples_loaded && audio_server_done) {
stop();
if (on_finished_playing)
on_finished_playing();
return;
}
bool enqueued = m_connection->try_enqueue(*m_next_buffer);
if (!enqueued)
return;
load_next_buffer();
if (audio_server_remaining_samples < m_device_samples_per_buffer) {
m_current_buffer = m_loader->get_more_samples(m_source_buffer_size_bytes);
if (m_current_buffer)
m_connection->enqueue(*m_current_buffer);
}
}

View file

@ -12,9 +12,6 @@
#include <LibAudio/Loader.h>
#include <LibCore/Timer.h>
#define PLAYBACK_MANAGER_BUFFER_SIZE 48 * KiB
#define PLAYBACK_MANAGER_RATE 44100
class PlaybackManager final {
public:
PlaybackManager(NonnullRefPtr<Audio::ClientConnection>);
@ -27,6 +24,7 @@ public:
void loop(bool);
bool toggle_pause();
void set_loader(NonnullRefPtr<Audio::Loader>&&);
size_t device_sample_rate() const { return m_device_sample_rate; }
int last_seek() const { return m_last_seek; }
bool is_paused() const { return m_paused; }
@ -42,18 +40,23 @@ public:
private:
void next_buffer();
void set_paused(bool);
void load_next_buffer();
void remove_dead_buffers();
bool m_paused { true };
bool m_loop = { false };
size_t m_next_ptr { 0 };
size_t m_last_seek { 0 };
float m_total_length { 0 };
// FIXME: Get this from the audio server
size_t m_device_sample_rate { 44100 };
size_t m_device_samples_per_buffer { 0 };
size_t m_source_buffer_size_bytes { 0 };
RefPtr<Audio::Loader> m_loader { nullptr };
NonnullRefPtr<Audio::ClientConnection> m_connection;
RefPtr<Audio::Buffer> m_next_buffer;
RefPtr<Audio::Buffer> m_current_buffer;
Vector<RefPtr<Audio::Buffer>> m_buffers;
RefPtr<Core::Timer> m_timer;
// Controls the GUI update rate. A smaller value makes the visualizations nicer.
static constexpr u32 update_rate_ms = 50;
// Number of milliseconds of audio data contained in each audio buffer
static constexpr u32 buffer_size_ms = 100;
};

View file

@ -49,7 +49,7 @@ SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window
// Set a temporary value for total samples.
// This value will be set properly when we load a new file.
const int total_samples = this->manager().total_length() * 44100;
const int total_samples = this->manager().total_length() * this->manager().device_sample_rate();
m_playback_progress_slider = m_player_view->add<AutoSlider>(Orientation::Horizontal);
m_playback_progress_slider->set_fixed_height(20);
@ -142,16 +142,20 @@ SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window
set_nonlinear_volume_slider(false);
manager().on_update = [&]() {
//TODO: make this program support other sample rates
int samples_played = client_connection().get_played_samples() + this->manager().last_seek();
int current_second = samples_played / 44100;
// Determine how many of the source file samples have played.
int samples_played = client_connection().get_played_samples();
float source_to_dest_ratio = static_cast<float>(loaded_file_samplerate()) / manager().device_sample_rate();
samples_played *= source_to_dest_ratio;
samples_played += this->manager().last_seek();
int current_second = samples_played / loaded_file_samplerate();
timestamp_label.set_text(String::formatted("Elapsed: {:02}:{:02}:{:02}", current_second / 3600, current_second / 60, current_second % 60));
if (!m_playback_progress_slider->mouse_is_down()) {
m_playback_progress_slider->set_value(samples_played);
}
dynamic_cast<Visualization*>(m_visualization.ptr())->set_buffer(this->manager().current_buffer());
dynamic_cast<Visualization*>(m_visualization.ptr())->set_samplerate(loaded_file_samplerate());
dynamic_cast<Visualization*>(m_visualization.ptr())->set_samplerate(manager().device_sample_rate());
};
manager().on_load_sample_buffer = [&](Audio::Buffer&) {
@ -176,6 +180,8 @@ SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window
} else
open_file((it + 1)->path);
}
m_stop_button->set_enabled(false);
};
}

View file

@ -63,4 +63,5 @@ private:
RefPtr<GUI::Label> m_volume_label;
bool m_nonlinear_volume_slider;
size_t m_device_sample_rate { 44100 };
};

View file

@ -20,7 +20,7 @@ void ClientConnection::enqueue(const Buffer& buffer)
auto success = enqueue_buffer(buffer.anonymous_buffer(), buffer.id(), buffer.sample_count());
if (success)
break;
sleep(.1);
usleep(100000);
}
}

View file

@ -30,7 +30,7 @@ WavLoaderPlugin::WavLoaderPlugin(const StringView& path)
if (!valid)
return;
m_resampler = make<ResampleHelper>(m_sample_rate, 44100);
m_resampler = make<ResampleHelper>(m_sample_rate, m_device_sample_rate);
}
WavLoaderPlugin::WavLoaderPlugin(const ByteBuffer& buffer)
@ -46,7 +46,7 @@ WavLoaderPlugin::WavLoaderPlugin(const ByteBuffer& buffer)
if (!valid)
return;
m_resampler = make<ResampleHelper>(m_sample_rate, 44100);
m_resampler = make<ResampleHelper>(m_sample_rate, m_device_sample_rate);
}
RefPtr<Buffer> WavLoaderPlugin::get_more_samples(size_t max_bytes_to_read_from_input)

View file

@ -41,6 +41,8 @@ public:
virtual bool has_error() override { return !m_error_string.is_null(); }
virtual const char* error_string() override { return m_error_string.characters(); }
// The Buffer returned contains input data resampled at the
// destination audio device sample rate.
virtual RefPtr<Buffer> get_more_samples(size_t max_bytes_to_read_from_input = 128 * KiB) override;
virtual void reset() override { return seek(0); }
@ -64,6 +66,11 @@ private:
OwnPtr<AK::InputStream> m_stream;
AK::InputMemoryStream* m_memory_stream;
String m_error_string;
// TODO: We should probably move resampling into the audio server.
//
// It would avoid duplicate resampling code and would allow clients
// to be agnostic of the destination audio device's sample rate.
OwnPtr<ResampleHelper> m_resampler;
u32 m_sample_rate { 0 };
@ -71,6 +78,8 @@ private:
PcmSampleFormat m_sample_format;
size_t m_byte_offset_of_data_samples { 0 };
// FIXME: Get this value from the audio server
int m_device_sample_rate { 44100 };
int m_loaded_samples { 0 };
int m_total_samples { 0 };
};