// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qalsaaudiodevices_p.h" #include "qmediadevices.h" #include "private/qalsaaudiosource_p.h" #include "private/qalsaaudiosink_p.h" #include "private/qalsaaudiodevice_p.h" #include QT_BEGIN_NAMESPACE namespace { struct free_char { void operator()(char *c) const { ::free(c); } }; using unique_str = std::unique_ptr; bool operator==(const unique_str &str, std::string_view sv) { return std::string_view{ str.get() } == sv; } bool operator!=(const unique_str &str, std::string_view sv) { return !(str == sv); } } // namespace QAlsaAudioDevices::QAlsaAudioDevices() : QPlatformAudioDevices() { } static QList availableDevices(QAudioDevice::Mode mode) { QList devices; // Create a list of all current audio devices that support mode void **hints; if (snd_device_name_hint(-1, "pcm", &hints) < 0) { qWarning() << "no alsa devices available"; return devices; } std::string_view filter = (mode == QAudioDevice::Input) ? "Input" : "Output"; QAlsaAudioDeviceInfo *sysdefault = nullptr; auto makeDeviceInfo = [&filter, mode](void *entry) -> std::unique_ptr { unique_str name{ snd_device_name_get_hint(entry, "NAME") }; if (name && name != "null") { unique_str descr{ snd_device_name_get_hint(entry, "DESC") }; unique_str io{ snd_device_name_get_hint(entry, "IOID") }; if (descr && (!io || (io == filter))) { auto info = std::make_unique( name.get(), QString::fromUtf8(descr.get()), mode); return info; } } return nullptr; }; bool hasDefault = false; void **n = hints; while (*n != NULL) { std::unique_ptr info = makeDeviceInfo(*n++); if (info) { if (!hasDefault && info->id.startsWith("default")) { info->isDefault = true; hasDefault = true; } if (!sysdefault && info->id.startsWith("sysdefault")) sysdefault = info.get(); devices.append(QAudioDevicePrivate::createQAudioDevice(std::move(info))); } } if (!hasDefault && sysdefault) { // Make "sysdefault" the default device if there is no "default" device exists sysdefault->isDefault = true; hasDefault = true; } if (!hasDefault && devices.size() > 0) { // forcefully declare the first device as "default" std::unique_ptr info = makeDeviceInfo(hints[0]); if (info) { info->isDefault = true; devices.prepend(QAudioDevicePrivate::createQAudioDevice(std::move(info))); } } snd_device_name_free_hint(hints); return devices; } QList QAlsaAudioDevices::findAudioInputs() const { return availableDevices(QAudioDevice::Input); } QList QAlsaAudioDevices::findAudioOutputs() const { return availableDevices(QAudioDevice::Output); } QPlatformAudioSource *QAlsaAudioDevices::createAudioSource(const QAudioDevice &deviceInfo, const QAudioFormat &fmt, QObject *parent) { return new QAlsaAudioSource(deviceInfo, fmt, parent); } QPlatformAudioSink *QAlsaAudioDevices::createAudioSink(const QAudioDevice &deviceInfo, const QAudioFormat &fmt, QObject *parent) { return new QAlsaAudioSink(deviceInfo, fmt, parent); } QT_END_NAMESPACE