AudioServer: Handle missing audio device gracefully

On several platforms, we don't yet have audio
support, so audio devices are missing. Instead of
having AudioServer crash repeatedly, and not
having the ability to open any app that relies on
it, we should instead handle missing devices
gracefully. This implementation is minimal, audio
device hotplugging support and such should be
implemented together with multi-device support.
AudioServer will start up and seem to function
normally without an audio device, but it will
pretend the device has a sample rate of 44.1 kHz
and all audio input is discarded.
This commit is contained in:
kleines Filmröllchen 2024-06-30 13:31:48 +02:00 committed by Nico Weber
parent e254810d0a
commit 9f0ab281ce
3 changed files with 28 additions and 8 deletions

View file

@ -20,7 +20,7 @@
namespace AudioServer {
Mixer::Mixer(NonnullRefPtr<Core::ConfigFile> config, NonnullOwnPtr<Core::File> device)
Mixer::Mixer(NonnullRefPtr<Core::ConfigFile> config, OwnPtr<Core::File> device)
: m_device(move(device))
, m_sound_thread(Threading::Thread::construct(
[this] {
@ -95,7 +95,8 @@ void Mixer::mix()
// Even though it's not realistic, the user expects no sound at 0%.
if (m_muted || m_main_volume < 0.01) {
m_device->write_until_depleted(m_zero_filled_buffer).release_value_but_fixme_should_propagate_errors();
if (m_device)
m_device->write_until_depleted(m_zero_filled_buffer).release_value_but_fixme_should_propagate_errors();
} else {
FixedMemoryStream stream { m_stream_buffer.span() };
@ -113,8 +114,9 @@ void Mixer::mix()
auto buffered_bytes = MUST(stream.tell());
VERIFY(buffered_bytes == m_stream_buffer.size());
m_device->write_until_depleted({ m_stream_buffer.data(), buffered_bytes })
.release_value_but_fixme_should_propagate_errors();
if (m_device)
m_device->write_until_depleted({ m_stream_buffer.data(), buffered_bytes })
.release_value_but_fixme_should_propagate_errors();
}
}
}
@ -152,6 +154,9 @@ void Mixer::set_muted(bool muted)
int Mixer::audiodevice_set_sample_rate(u32 sample_rate)
{
if (!m_device)
return ENOENT;
int code = ioctl(m_device->fd(), SOUNDCARD_IOCTL_SET_SAMPLE_RATE, sample_rate);
if (code != 0)
dbgln("Error while setting sample rate to {}: ioctl error: {}", sample_rate, strerror(errno));
@ -165,6 +170,11 @@ u32 Mixer::audiodevice_get_sample_rate() const
{
if (m_cached_sample_rate.has_value())
return m_cached_sample_rate.value();
// We pretend that a non-existent device has a common sample rate (instead of returning something like 0 that would break clients).
if (!m_device)
return 44100;
u32 sample_rate = 0;
int code = ioctl(m_device->fd(), SOUNDCARD_IOCTL_GET_SAMPLE_RATE, &sample_rate);
if (code != 0)

View file

@ -42,7 +42,12 @@ public:
static ErrorOr<NonnullRefPtr<Mixer>> try_create(NonnullRefPtr<Core::ConfigFile> config)
{
// FIXME: Allow AudioServer to use other audio channels as well
auto device = TRY(Core::File::open("/dev/audio/0"sv, Core::File::OpenMode::Write));
auto maybe_device = Core::File::open("/dev/audio/0"sv, Core::File::OpenMode::Write);
OwnPtr<Core::File> device;
if (maybe_device.is_error())
dbgln("Couldn't open first audio channel: {}", maybe_device.error());
else
device = maybe_device.release_value();
return adopt_nonnull_ref_or_enomem(new (nothrow) Mixer(move(config), move(device)));
}
@ -61,7 +66,7 @@ public:
u32 audiodevice_get_sample_rate() const;
private:
Mixer(NonnullRefPtr<Core::ConfigFile> config, NonnullOwnPtr<Core::File> device);
Mixer(NonnullRefPtr<Core::ConfigFile> config, OwnPtr<Core::File> device);
void request_setting_sync();
@ -69,7 +74,7 @@ private:
Threading::Mutex m_pending_mutex;
Threading::ConditionVariable m_mixing_necessary { m_pending_mutex };
NonnullOwnPtr<Core::File> m_device;
OwnPtr<Core::File> m_device;
mutable Optional<u32> m_cached_sample_rate {};
NonnullRefPtr<Threading::Thread> m_sound_thread;

View file

@ -19,7 +19,12 @@ ErrorOr<int> serenity_main(Main::Arguments)
auto config = TRY(Core::ConfigFile::open_for_app("Audio", Core::ConfigFile::AllowWriting::Yes));
TRY(Core::System::unveil(config->filename(), "rwc"sv));
TRY(Core::System::unveil("/dev/audio", "wc"));
auto audio_unveil_result = Core::System::unveil("/dev/audio", "wc");
// System may not have audio devices, which we handle gracefully.
if (audio_unveil_result.is_error())
dbgln("Couldn't unveil audio devices: {}", audio_unveil_result.error());
TRY(Core::System::unveil(nullptr, nullptr));
Core::EventLoop event_loop;