Final version that lets the user select the ALSA device with a combo box

This version lets the user select the ALSA device to use with a combo
box. It does not check whether the device works for the parameters that
LMMS uses (SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_FORMAT_S16_LE, etc.).
Doing these checks while compiling the list of available devices led to
strange effects which are likely caused by the fact that the PCM device
has to be opened to query its capabilities. This in turn led to error
messages a la "Resource or device busy" when testing the new
functionality repeatedly.

However, having a combo box to select devices from should be a good step
forward compared to a simple line edit. :)
This commit is contained in:
Michael Gregorius
2015-06-27 20:07:26 +02:00
parent 27653b713b
commit 37c4da81b5
5 changed files with 113 additions and 169 deletions

View File

@@ -47,34 +47,19 @@ public:
class DeviceInfo
{
public:
DeviceInfo(int cardNumber, int deviceNumber,
QString const & cardName, QString const & pcmName,
QString const & cardId, QString const & pcmId) :
m_cardNumber(cardNumber),
m_deviceNumber(deviceNumber),
m_cardName(cardName),
m_pcmName(pcmName),
m_cardId(cardId),
m_pcmId(pcmId)
DeviceInfo(QString const & deviceName, QString const & deviceDescription) :
m_deviceName(deviceName),
m_deviceDescription(deviceDescription)
{}
~DeviceInfo() {}
int getCardNumber() const { return m_cardNumber; }
int getDeviceNumber() const { return m_deviceNumber; }
QString const & getCardName() const { return m_cardName; }
QString const & getPcmName() const { return m_pcmName; }
QString const & getCardId() const { return m_cardId; }
QString const & getPcmId() const { return m_pcmId; }
QString getHWString() const { return QString("hw:%1,%2").arg(m_cardNumber).arg(m_deviceNumber); }
QString const & getDeviceName() const { return m_deviceName; }
QString const & getDeviceDescription() const { return m_deviceDescription; }
private:
int m_cardNumber;
int m_deviceNumber;
QString m_cardName;
QString m_pcmName;
QString m_cardId;
QString m_pcmId;
QString m_deviceName;
QString m_deviceDescription;
};
typedef std::vector<DeviceInfo> DeviceInfoCollection;

View File

@@ -1,7 +1,7 @@
/*
* AudioAlsa.h - device-class that implements ALSA-PCM-output
* AudioDeviceSetupWidget.h - Implements a setup widget for ALSA-PCM-output
*
* Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2004-2015 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - http://lmms.io
*
@@ -33,12 +33,9 @@
#include "AudioAlsa.h"
#include <vector>
class QComboBox;
class LcdSpinBox;
class QLineEdit;
class AudioAlsaSetupWidget : public AudioDeviceSetupWidget
@@ -56,7 +53,6 @@ public slots:
private:
QComboBox * m_deviceComboBox;
QLineEdit * m_device;
LcdSpinBox * m_channels;
int m_selectedDevice;

View File

@@ -1,7 +1,7 @@
/*
* AudioDevice.h - base-class for audio-devices, used by LMMS-mixer
* AudioDeviceSetupWidget.h - Base class for audio device setup widgets
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2004-2015 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - http://lmms.io
*

View File

@@ -141,146 +141,113 @@ QString AudioAlsa::probeDevice()
// TODO Test code. Delete!
void device_list(void)
/**
* @brief Checks whether the ALSA device with the given name has the needed
* capabilities for LMMS.
* @param deviceName Name of the device that is checked.
* @return If the device is usable for LMMS <tt>true</tt> is returned.
*/
bool hasCapabilities(char *device_name)
{
snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
snd_ctl_t *handle;
int card, err, dev, idx;
snd_ctl_card_info_t *info;
snd_pcm_info_t *pcminfo;
snd_ctl_card_info_alloca(&info);
snd_pcm_info_alloca(&pcminfo);
snd_pcm_t *pcm; // PCM handle
snd_pcm_hw_params_t *hw_params;
int err;
card = -1;
if (snd_card_next(&card) < 0 || card < 0) {
return;
// Implicit check for SND_PCM_STREAM_PLAYBACK
err = snd_pcm_open(&pcm, device_name, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0)
{
std::cerr << "Cannot open device '" << device_name << "': " << snd_strerror(err) << std::endl;
return false;
}
std::cout << "**** List of " << snd_pcm_stream_name(stream) << " Hardware Devices ****\n";
while (card >= 0) {
char name[32];
sprintf(name, "hw:%d", card);
if ((err = snd_ctl_open(&handle, name, 0)) < 0) {
// TODO Error handling
//error("control open (%i): %s", card, snd_strerror(err));
goto next_card;
}
if ((err = snd_ctl_card_info(handle, info)) < 0) {
// TODO Error handling
//error("control hardware info (%i): %s", card, snd_strerror(err));
snd_ctl_close(handle);
goto next_card;
}
dev = -1;
while (1) {
unsigned int count;
if (snd_ctl_pcm_next_device(handle, &dev)<0)
// TODO Error handling
//error("snd_ctl_pcm_next_device");
std::cerr << "snd_ctl_pcm_next_device";
if (dev < 0)
break;
snd_pcm_info_set_device(pcminfo, dev);
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, stream);
if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
if (err != -ENOENT)
// TODO Error handling
//error("control digital audio info (%i): %s", card, snd_strerror(err));
std::cerr << "Error\n";
continue;
}
//std::cout << "card " << card << ": " << snd_ctl_card_info_get_id(info) << " [" << snd_ctl_card_info_get_name(info) << "], device " << dev <<
// ": " << snd_pcm_info_get_id(pcminfo) << " [" << snd_pcm_info_get_name(pcminfo) << "]\n";
std::cout << "card hw" << card << ":" << dev << " - " << "[" << snd_ctl_card_info_get_name(info) << "/" << snd_pcm_info_get_name(pcminfo) << "], " <<
snd_ctl_card_info_get_id(info) << "," << snd_pcm_info_get_id(pcminfo) << "\n";
count = snd_pcm_info_get_subdevices_count(pcminfo);
std::cout << " Subdevices: " << snd_pcm_info_get_subdevices_avail(pcminfo) << "/" << count << std::endl;
for (idx = 0; idx < (int)count; idx++) {
snd_pcm_info_set_subdevice(pcminfo, idx);
if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
// TODO Error handling
//error("control digital audio playback info (%i): %s", card, snd_strerror(err));
std::cerr << "Error\n";
} else {
std::cout << " Subdevice #" << idx << ": " << snd_pcm_info_get_subdevice_name(pcminfo) << std::endl;
}
}
}
snd_ctl_close(handle);
next_card:
if (snd_card_next(&card) < 0) {
// TODO Error handling
//error("snd_card_next");
break;
}
snd_pcm_hw_params_alloca(&hw_params);
err = snd_pcm_hw_params_any(pcm, hw_params);
if (err < 0)
{
std::cerr << "Cannot get hardware parameters: " << snd_strerror(err) << std::endl;
snd_pcm_close(pcm);
return false;
}
// Checks for SND_PCM_ACCESS_RW_INTERLEAVED
err = snd_pcm_hw_params_test_access(pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0)
{
std::cerr << "Interleaved access not possible for '" << device_name << "': " << snd_strerror(err) << std::endl;
snd_pcm_close(pcm);
return false;
}
// Check for SND_PCM_FORMAT_S16_LE or SND_PCM_FORMAT_S16_BE
bool validFormatFound = false;
validFormatFound |= !snd_pcm_hw_params_test_format(pcm, hw_params, SND_PCM_FORMAT_S16_LE);
validFormatFound |= !snd_pcm_hw_params_test_format(pcm, hw_params, SND_PCM_FORMAT_S16_BE);
if (!validFormatFound)
{
std::cerr << "Device " << device_name << " does not not support SND_PCM_FORMAT_S16_LE or SND_PCM_FORMAT_S16_BE!" << std::endl;
snd_pcm_close(pcm);
return false;
}
snd_pcm_close(pcm);
return true;
}
/**
* @brief Creates a list of all available devices.
*
* Uses the hints API of ALSA to collect all devices. This also includes plug
* devices. The reason to collect these and not the raw hardware devices
* (e.g. hw:0,0) is that hardware devices often have a very limited number of
* supported formats, etc. Plugs on the other hand are software components that
* map all types of formats and inputs to the hardware and therefore they are
* much more flexible and more what we want.
*
* Further helpful info http://jan.newmarch.name/LinuxSound/Sampled/Alsa/.
*
* @return A collection of devices found on the system.
*/
AudioAlsa::DeviceInfoCollection AudioAlsa::getAvailableDevices()
{
DeviceInfoCollection deviceInfos;
snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
snd_ctl_t *handle;
snd_ctl_card_info_t *info;
snd_pcm_info_t *pcminfo;
char **hints;
// Allocate memory for the info structs
snd_ctl_card_info_alloca(&info);
snd_pcm_info_alloca(&pcminfo);
int card = -1;
while (!snd_card_next(&card) && card >= 0)
/* Enumerate sound devices */
int err = snd_device_name_hint(-1, "pcm", (void***)&hints);
if (err != 0)
{
std::cout << "Card: " << card << " found!" << std::endl;
char name[32];
sprintf(name, "hw:%d", card);
if (snd_ctl_open(&handle, name, 0) < 0)
{
std::cerr << "Error opening ALSA card " << name << std::endl;
continue;
}
if (snd_ctl_card_info(handle, info) < 0)
{
snd_ctl_close(handle);
std::cerr << "Could not retrieve info for ALSA card " << name << std::endl;
continue;
}
int dev = -1;
while (!snd_ctl_pcm_next_device(handle, &dev) && dev >= 0)
{
snd_pcm_info_set_device(pcminfo, dev);
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, stream);
if (!snd_ctl_pcm_info(handle, pcminfo))
{
QString cardName(snd_ctl_card_info_get_name(info));
QString pcmName(snd_pcm_info_get_name(pcminfo));
QString cardId(snd_ctl_card_info_get_id(info));
QString pcmId(snd_pcm_info_get_id(pcminfo));
DeviceInfo currentDevice(card, dev, cardName, pcmName, cardId, pcmId);
deviceInfos.push_back(currentDevice);
std::cout << "card hw" << card << ":" << dev << " - " << "[" << snd_ctl_card_info_get_name(info) << " | " << snd_pcm_info_get_name(pcminfo) << "], " <<
snd_ctl_card_info_get_id(info) << ", " << snd_pcm_info_get_id(pcminfo) << std::endl;
}
}
snd_ctl_close(handle);
return deviceInfos;
}
char** n = hints;
while (*n != NULL)
{
char *name = snd_device_name_get_hint(*n, "NAME");
char *description = snd_device_name_get_hint(*n, "DESC");
// We could call hasCapabilities(name) here but this gives strange
// results
if (name != 0 && description != 0)
{
deviceInfos.push_back(DeviceInfo(QString(name), QString(description)));
}
free(name);
free(description);
n++;
}
//Free the hint buffer
snd_device_name_free_hint((void**)hints);
return deviceInfos;
}

View File

@@ -1,7 +1,7 @@
/*
* AudioAlsa.cpp - device-class which implements ALSA-PCM-output
* AudioAlsaSetupWidget.cpp - Implements a setup widget for ALSA-PCM-output
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2004-2015 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - http://lmms.io
*
@@ -23,14 +23,14 @@
*/
#include <QComboBox>
#include <QLineEdit>
#include <QLabel>
#include "AudioAlsa.h"
#include "AudioAlsaSetupWidget.h"
#ifdef LMMS_HAVE_ALSA
#include "AudioAlsa.h"
#include "ConfigManager.h"
#include "LcdSpinBox.h"
#include "gui_templates.h"
@@ -47,18 +47,17 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) :
QString deviceText = ConfigManager::inst()->value( "audioalsa", "device" );
// Implements the "-l" from aplay
//device_list();
m_deviceComboBox = new QComboBox(this);
for (size_t i = 0; i < m_deviceInfos.size(); ++i)
{
AudioAlsa::DeviceInfo const & currentDeviceInfo = m_deviceInfos[i];
QString comboBoxText = currentDeviceInfo.getHWString() + " [" + currentDeviceInfo.getCardName() + " | " + currentDeviceInfo.getPcmName() + "]";
QString comboBoxText = currentDeviceInfo.getDeviceName();
m_deviceComboBox->addItem(comboBoxText, QVariant(static_cast<uint>(i)));
m_deviceComboBox->setItemData(i, comboBoxText, Qt::ToolTipRole);
if (currentDeviceInfo.getHWString() == deviceText)
QString toolTipText = currentDeviceInfo.getDeviceDescription();
m_deviceComboBox->setItemData(i, toolTipText, Qt::ToolTipRole);
if (currentDeviceInfo.getDeviceName() == deviceText)
{
m_deviceComboBox->setCurrentIndex(static_cast<int>(i));
}
@@ -71,9 +70,6 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) :
SIGNAL(currentIndexChanged(int)),
SLOT(onCurrentIndexChanged(int)));
//m_device = new QLineEdit( AudioAlsa::probeDevice(), this );
//m_device->setGeometry( 10, 20, 160, 20 );
QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this );
dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
dev_lbl->setGeometry( 10, 40, 160, 10 );
@@ -109,7 +105,7 @@ void AudioAlsaSetupWidget::saveSettings()
if (m_selectedDevice != -1)
{
AudioAlsa::DeviceInfo const & selectedDevice = m_deviceInfos[m_selectedDevice];
deviceText = selectedDevice.getHWString();
deviceText = selectedDevice.getDeviceName();
}
ConfigManager::inst()->setValue( "audioalsa", "device", deviceText );