2025-09-17 Fred Gleason <fredg@paravelsystems.com>

* Added a 'librdalsa' convenience library in 'rdalsa/'.
	* Moved the 'RDAlsaCard' class from 'utils/rdalsaconfig/' to
	'rdalsa/' to contain ALSA card quirk information.
	* Refactored the ALSA driver in caed(8) to use 'RDAlsaCard'.

Signed-off-by: Fred Gleason <fredg@paravelsystems.com>
This commit is contained in:
Fred Gleason
2025-09-17 17:17:09 -04:00
parent 1d608b5321
commit e5340cfce8
13 changed files with 218 additions and 64 deletions

View File

@@ -25051,3 +25051,8 @@
2025-09-15 Fred Gleason <fredg@paravelsystems.com>
* Fixed a regression in 'lib/rdairplay_conf.cpp' that caused database
corruption when adding new host records.
2025-09-17 Fred Gleason <fredg@paravelsystems.com>
* Added a 'librdalsa' convenience library in 'rdalsa/'.
* Moved the 'RDAlsaCard' class from 'utils/rdalsaconfig/' to
'rdalsa/' to contain ALSA card quirk information.
* Refactored the ALSA driver in caed(8) to use 'RDAlsaCard'.

View File

@@ -24,6 +24,7 @@ if HPI_RD_AM
endif
if ALSA_RD_AM
ALSACONFIG_RD_OPT = rdalsaconfig
RDALSA_RD_OPT = rdalsa
endif
SUBDIRS = versions\
@@ -32,7 +33,7 @@ SUBDIRS = versions\
helpers\
LICENSES\
lib\
$(HPI_RD_OPT) apis\
$(HPI_RD_OPT) $(RDALSA_RD_OPT) apis\
scripts\
conf\
docs\

View File

@@ -1,8 +1,8 @@
## automake.am
## Makefile.am
##
## Core Audio Engine Makefile.am for Rivendell
##
## Copyright 2002-2023 Fred Gleason <fredg@paravelsystems.com>
## Copyright 2002-2025 Fred Gleason <fredg@paravelsystems.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License version 2 as
@@ -19,8 +19,8 @@
##
## Use automake to process this into a Makefile.in
AM_CPPFLAGS = -Wall -I$(top_srcdir)/lib -I$(top_srcdir)/rdhpi -Wno-strict-aliasing -std=c++11 -fPIC @QT5_CFLAGS@ @MUSICBRAINZ_CFLAGS@ @IMAGEMAGICK_CFLAGS@ @LIBJACK_CFLAGS@
LIBS = -L$(top_srcdir)/lib -L$(top_srcdir)/rdhpi
AM_CPPFLAGS = -Wall -I$(top_srcdir)/lib -I$(top_srcdir)/rdhpi -I$(top_srcdir)/rdalsa -Wno-strict-aliasing -std=c++11 -fPIC @QT5_CFLAGS@ @MUSICBRAINZ_CFLAGS@ @IMAGEMAGICK_CFLAGS@ @LIBJACK_CFLAGS@
LIBS = -L$(top_srcdir)/lib -L$(top_srcdir)/rdhpi -L$(top_srcdir)/rdalsa
MOC = @QT_MOC@
# The dependency for qt's Meta Object Compiler (moc)

View File

@@ -2,7 +2,7 @@
//
// caed(8) driver for Advanced Linux Audio Architecture devices
//
// (C) Copyright 2021 Fred Gleason <fredg@paravelsystems.com>
// (C) Copyright 2021-2025 Fred Gleason <fredg@paravelsystems.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@@ -662,53 +662,68 @@ QString DriverAlsa::version() const
bool DriverAlsa::initialize(unsigned *next_cardnum)
{
#ifdef ALSA
QString dev;
QString card_id;
snd_pcm_t *pcm_play_handle;
snd_pcm_t *pcm_capture_handle;
snd_ctl_t *snd_ctl;
snd_ctl_t *snd_ctl=NULL;
snd_ctl_card_info_t *card_info=NULL;
bool pcm_opened=false;
int card=0;
RDAlsaCard *alsacard=NULL;
//
// Start Up Interfaces
//
while((*next_cardnum)<RD_MAX_CARDS) {
//
// Open the Control Interface
//
rda->station()->setCardDriver(*next_cardnum,RDStation::Alsa);
card_id=QString::asprintf("rd%d",card);
if(snd_ctl_open(&snd_ctl,card_id.toUtf8(),0)<0) {
rda->syslog(LOG_INFO,
"no control device found for %s",
card_id.toUtf8().constData());
alsacard=new RDAlsaCard(card_id,card);
}
else {
alsacard=new RDAlsaCard(snd_ctl,card);
snd_ctl_card_info_malloc(&card_info);
snd_ctl_card_info(snd_ctl,card_info);
rda->station()->setCardName(*next_cardnum,alsacard->longName());
snd_ctl_close(snd_ctl);
}
//
// Open the PCM
//
pcm_opened=false;
alsa_play_format[*next_cardnum].exiting = true;
alsa_capture_format[*next_cardnum].exiting = true;
dev=QString::asprintf("rd%d",card);
if(snd_pcm_open(&pcm_play_handle,dev.toUtf8(),
if(snd_pcm_open(&pcm_play_handle,card_id.toUtf8(),
SND_PCM_STREAM_PLAYBACK,0)==0){
pcm_opened=true;
if(!AlsaStartPlayDevice(dev,*next_cardnum,pcm_play_handle)) {
if(!AlsaStartPlayDevice(card_id,*next_cardnum,pcm_play_handle,alsacard)) {
snd_pcm_close(pcm_play_handle);
}
}
if(snd_pcm_open(&pcm_capture_handle,dev.toUtf8(),
if(snd_pcm_open(&pcm_capture_handle,card_id.toUtf8(),
SND_PCM_STREAM_CAPTURE,0)==0) {
pcm_opened=true;
if(!AlsaStartCaptureDevice(dev,*next_cardnum,pcm_capture_handle)) {
if(!AlsaStartCaptureDevice(card_id,*next_cardnum,pcm_capture_handle,
alsacard)) {
snd_pcm_close(pcm_capture_handle);
}
}
if(!pcm_opened) {
delete alsacard;
alsacard=NULL;
return card>0;
}
rda->station()->setCardDriver(*next_cardnum,RDStation::Alsa);
if(snd_ctl_open(&snd_ctl,dev.toUtf8(),0)<0) {
rda->syslog(LOG_INFO,
"no control device found for %s",
dev.toUtf8().constData());
rda->station()->setCardName(*next_cardnum,tr("ALSA Device")+" "+dev);
}
else {
snd_ctl_card_info_malloc(&card_info);
snd_ctl_card_info(snd_ctl,card_info);
rda->station()->
setCardName(*next_cardnum,snd_ctl_card_info_get_longname(card_info));
snd_ctl_close(snd_ctl);
}
rda->station()->setCardName(*next_cardnum,alsacard->prettyLongName());
delete alsacard;
alsacard=NULL;
alsa_input_port_quantities[*next_cardnum]=
alsa_capture_format[*next_cardnum].channels/RD_DEFAULT_CHANNELS;
rda->station()->setCardInputs(*next_cardnum,
@@ -720,9 +735,6 @@ bool DriverAlsa::initialize(unsigned *next_cardnum)
alsa_output_port_quantities.value(*next_cardnum));
card++;
if(!pcm_opened) {
return card>0;
}
addCard(*next_cardnum);
(*next_cardnum)++;
}
@@ -1414,7 +1426,8 @@ void DriverAlsa::recordTimerData(int cardport)
#ifdef ALSA
bool DriverAlsa::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm)
bool DriverAlsa::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm,
RDAlsaCard *alsacard)
{
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
@@ -1430,6 +1443,8 @@ bool DriverAlsa::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm)
rda->syslog(LOG_INFO,"Starting ALSA Capture Device %s:",
(const char *)dev.toUtf8());
rda->syslog(LOG_INFO," Native Device Name = %s",
alsacard->id().toUtf8().constData());
//
// Access Type
@@ -1492,11 +1507,11 @@ bool DriverAlsa::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm)
//
// Channels
//
if(rda->config()->alsaChannelsPerPcm()<0) {
if(alsacard->maxChannelsPerPcm()<0) {
alsa_capture_format[card].channels=RD_DEFAULT_CHANNELS*RD_MAX_PORTS;
}
else {
alsa_capture_format[card].channels=rda->config()->alsaChannelsPerPcm();
alsa_capture_format[card].channels=alsacard->maxChannelsPerPcm();
}
snd_pcm_hw_params_set_channels_near(pcm,hwparams,
&alsa_capture_format[card].channels);
@@ -1581,7 +1596,8 @@ bool DriverAlsa::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm)
}
bool DriverAlsa::AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm)
bool DriverAlsa::AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm,
RDAlsaCard *alsacard)
{
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
@@ -1597,6 +1613,8 @@ bool DriverAlsa::AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm)
rda->syslog(LOG_INFO,"Starting ALSA Play Device %s:",
(const char *)dev.toUtf8());
rda->syslog(LOG_INFO," Native Device Name = %s",
alsacard->id().toUtf8().constData());
//
// Access Type
@@ -1654,11 +1672,11 @@ bool DriverAlsa::AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm)
//
// Channels
//
if(rda->config()->alsaChannelsPerPcm()<0) {
if(alsacard->maxChannelsPerPcm()<0) {
alsa_play_format[card].channels=RD_DEFAULT_CHANNELS*RD_MAX_PORTS;
}
else {
alsa_play_format[card].channels=rda->config()->alsaChannelsPerPcm();
alsa_play_format[card].channels=alsacard->maxChannelsPerPcm();
}
snd_pcm_hw_params_set_channels_near(pcm,hwparams,
&alsa_play_format[card].channels);

View File

@@ -2,7 +2,7 @@
//
// caed(8) driver for Advanced Linux Audio Architecture devices
//
// (C) Copyright 2021 Fred Gleason <fredg@paravelsystems.com>
// (C) Copyright 2021-2025 Fred Gleason <fredg@paravelsystems.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@@ -23,6 +23,7 @@
#include <QMap>
#include <rdalsacard.h>
#include <rdconfig.h>
#include <rdwavefile.h>
@@ -98,8 +99,10 @@ class DriverAlsa : public Driver
private:
#ifdef ALSA
bool AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm);
bool AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm);
bool AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm,
RDAlsaCard *alsacard);
bool AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm,
RDAlsaCard *alsacard);
void AlsaInitCallback();
int GetAlsaOutputStream(int card);
void FreeAlsaOutputStream(int card,int stream);

View File

@@ -502,7 +502,7 @@ if test $ALSA_FOUND ; then
AC_MSG_ERROR([*** libsamplerate not found, but is needed for ALSA support ***])
fi
AC_DEFINE(ALSA,yes)
AC_SUBST(LIBALSA,-lasound)
AC_SUBST(LIBALSA,"-lasound -lrdalsa")
SRC_NEEDED=yes
USING_ALSA=yes
else
@@ -664,6 +664,7 @@ AC_CONFIG_FILES([rivendell.spec \
rdadmin/Makefile \
ripcd/Makefile \
rdairplay/Makefile \
rdalsa/Makefile \
rdcartslots/Makefile \
rdcastmanager/Makefile \
rdcatch/Makefile \

56
rdalsa/Makefile.am Normal file
View File

@@ -0,0 +1,56 @@
## Makefile.am
##
## Makefile.am for rivendell/rdalsa
##
## by Fred Gleason <fredg@paravelsystems.com>
##
## (C) Copyright 2025 Fred Gleason <fredg@paravelsystems.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License version 2 as
## published by the Free Software Foundation.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public
## License along with this program; if not, write to the Free Software
## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
##
## Use automake to process this into a Makefile.in
AM_CPPFLAGS = -Wall -DPREFIX=\"$(prefix)\" -Wno-strict-aliasing -std=c++11 -fPIC -I$(top_srcdir)/lib @QT5_CFLAGS@ @IMAGEMAGICK_CFLAGS@
LIBS = -L$(top_srcdir)/lib
MOC = @QT_MOC@
CWRAP = ../helpers/cwrap
# The dependency for qt's Meta Object Compiler (moc)
moc_%.cpp: %.h
$(MOC) $< -o $@
# The cwrap dependency
html_%.cpp: %.html
$(CWRAP) -o $@ $<
lib_LTLIBRARIES = librdalsa.la
dist_librdalsa_la_SOURCES = rdalsacard.cpp rdalsacard.h
librdalsa_la_LDFLAGS = -release $(VERSION)
CLEANFILES = *~\
*.idb\
*.ilk\
*.lib\
*.obj\
*.pdb\
*.qm\
moc_*
DISTCLEAN = Makefile
MAINTAINERCLEANFILES = *~\
Makefile\
Makefile.in\
moc_*

View File

@@ -18,35 +18,63 @@
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
#include <QObject>
#include <rdapplication.h>
#include "rdalsacard.h"
RDAlsaCard::RDAlsaCard(snd_ctl_t *ctl,int index)
{
snd_ctl_card_info_t *card_info;
snd_pcm_info_t *pcm_info;
card_enabled=false;
card_index=index;
snd_ctl_card_info_t *card_info;
snd_ctl_card_info_malloc(&card_info);
snd_pcm_info_malloc(&pcm_info);
snd_ctl_card_info(ctl,card_info);
card_id=QString(snd_ctl_card_info_get_id(card_info));
card_driver=QString(snd_ctl_card_info_get_driver(card_info));
card_name=QString(snd_ctl_card_info_get_name(card_info));
card_pretty_name=card_name;
card_long_name=QString(snd_ctl_card_info_get_longname(card_info));
card_pretty_long_name=card_long_name;
card_mixer_name=QString(snd_ctl_card_info_get_mixername(card_info));
card_pretty_mixer_name=card_mixer_name;
card_max_channels_per_pcm=rda->config()->alsaChannelsPerPcm();
//
// Apply Specific Device Quirks
//
if(card_name=="Loopback") { // Fix the opaque name assigned by Wheatstone
card_name.replace("Loopback","WheatNet");
card_long_name.replace("Loopback","WheatNet");
card_mixer_name.replace("Loopback","WheatNet");
card_pretty_name.replace("Loopback","WheatNet");
card_pretty_long_name.replace("Loopback","WheatNet");
card_pretty_mixer_name.replace("Loopback","WheatNet");
if(card_max_channels_per_pcm<0) {
card_max_channels_per_pcm=2;
}
}
card_enabled=false;
snd_pcm_info_free(pcm_info);
snd_ctl_card_info_free(card_info);
}
RDAlsaCard::RDAlsaCard(const QString &id,int index)
{
card_id=id;
card_index=index;
card_driver=QObject::tr("ALSA Driver");
card_name=card_id;
card_pretty_name=card_name;
card_long_name=QObject::tr("ALSA Device")+" "+card_id;
card_pretty_long_name=card_long_name;
card_mixer_name=QObject::tr("[none]");
card_pretty_mixer_name=card_mixer_name;
card_max_channels_per_pcm=rda->config()->alsaChannelsPerPcm();
}
int RDAlsaCard::index() const
{
return card_index;
@@ -71,18 +99,36 @@ QString RDAlsaCard::name() const
}
QString RDAlsaCard::prettyName() const
{
return card_pretty_name;
}
QString RDAlsaCard::longName() const
{
return card_long_name;
}
QString RDAlsaCard::prettyLongName() const
{
return card_pretty_long_name;
}
QString RDAlsaCard::mixerName() const
{
return card_long_name;
}
QString RDAlsaCard::prettyMixerName() const
{
return card_pretty_mixer_name;
}
bool RDAlsaCard::isEnabled() const
{
return card_enabled;
@@ -95,14 +141,29 @@ void RDAlsaCard::setEnabled(bool state)
}
int RDAlsaCard::maxChannelsPerPcm() const
{
return card_max_channels_per_pcm;
}
QString RDAlsaCard::dump() const
{
QString ret=QString::asprintf("Card %d\n",index());
ret+=" ID: "+id()+"\n";
ret+=" Name: "+name()+"\n";
ret+=" LongName: "+longName()+"\n";
ret+=" MixerName: "+mixerName()+"\n";
ret+=" Pretty Name: "+prettyName()+"\n";
ret+=" Long Name: "+longName()+"\n";
ret+=" Pretty Long Name: "+prettyLongName()+"\n";
ret+=" Driver: "+driver()+"\n";
ret+=" Mixer Name: "+mixerName()+"\n";
ret+=" Pretty Mixer Name: "+prettyMixerName()+"\n";
if(maxChannelsPerPcm()<0) {
ret+=" Max Channels Per PCM: [default]\n";
}
else {
ret+=QString::asprintf(" Max Channels Per PCM: %d\n",maxChannelsPerPcm());
}
return ret;
}

View File

@@ -21,23 +21,29 @@
#ifndef RDALSACARD_H
#define RDALSACARD_H
#ifdef ALSA
#include <alsa/asoundlib.h>
#include <qstring.h>
#include <qstringlist.h>
#include <QString>
#include <QStringList>
class RDAlsaCard
{
public:
RDAlsaCard(snd_ctl_t *ctl,int index);
RDAlsaCard(const QString &id,int index);
int index() const;
QString id() const;
QString driver() const;
QString name() const;
QString prettyName() const;
QString longName() const;
QString prettyLongName() const;
QString mixerName() const;
QString prettyMixerName() const;
bool isEnabled() const;
void setEnabled(bool state);
int maxChannelsPerPcm() const;
QString dump() const;
private:
@@ -45,10 +51,14 @@ class RDAlsaCard
QString card_id;
QString card_driver;
QString card_name;
QString card_pretty_name;
QString card_long_name;
QString card_pretty_long_name;
QString card_mixer_name;
QString card_pretty_mixer_name;
int card_max_channels_per_pcm;
bool card_enabled;
};
#endif // ALSA
#endif // RDALSACARD_H

View File

@@ -1,6 +1,6 @@
## Makefile.am
##
## (C) Copyright 2009-2022 Fred Gleason <fredg@paravelsystems.com>
## (C) Copyright 2009-2025 Fred Gleason <fredg@paravelsystems.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License version 2 as
@@ -18,8 +18,8 @@
## Use automake to process this into a Makefile.in
##
AM_CPPFLAGS = -Wall -DPREFIX=\"$(prefix)\" -Wno-strict-aliasing -std=c++11 -fPIC -I$(top_srcdir)/lib -I$(top_srcdir)/rdhpi @QT5_CFLAGS@ @MUSICBRAINZ_CFLAGS@ @IMAGEMAGICK_CFLAGS@
LIBS = -L$(top_srcdir)/lib -L$(top_srcdir)/rdhpi
AM_CPPFLAGS = -Wall -DPREFIX=\"$(prefix)\" -Wno-strict-aliasing -std=c++11 -fPIC -I$(top_srcdir)/lib -I$(top_srcdir)/rdalsa @QT5_CFLAGS@ @MUSICBRAINZ_CFLAGS@ @IMAGEMAGICK_CFLAGS@
LIBS = -L$(top_srcdir)/lib -L$(top_srcdir)/rdalsa
MOC = @QT_MOC@
# The dependency for qt's Meta Object Compiler (moc)
@@ -29,7 +29,6 @@ moc_%.cpp: %.h
bin_PROGRAMS = rdalsaconfig
dist_rdalsaconfig_SOURCES = alsaitem.cpp alsaitem.h\
rdalsacard.cpp rdalsacard.h\
rdalsamodel.cpp rdalsamodel.h\
rdalsaconfig.cpp rdalsaconfig.h

View File

@@ -55,7 +55,7 @@ QVariant RDAlsaModel::data(const QModelIndex &index,int role) const
switch((Qt::ItemDataRole)role) {
case Qt::DisplayRole:
return QVariant(model_alsa_cards.at(row)->longName());
return QVariant(model_alsa_cards.at(row)->prettyLongName());
break;
case Qt::DecorationRole:

View File

@@ -27,7 +27,7 @@
#include <rd.h>
#include "rdalsacard.h"
#include <rdalsacard.h>
#define START_MARKER "# *** Start of Rivendell configuration generated by rdalsaconfig(1) ***"
#define END_MARKER "# *** End of Rivendell configuration generated by rdalsaconfig(1) ***"

View File

@@ -1,6 +1,6 @@
## Makefile.am
##
## (C) Copyright 2009-2022 Fred Gleason <fredg@paravelsystems.com>
## (C) Copyright 2009-2025 Fred Gleason <fredg@paravelsystems.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License version 2 as
@@ -18,8 +18,8 @@
## Use automake to process this into a Makefile.in
##
AM_CPPFLAGS = -Wall -DPREFIX=\"$(prefix)\" -Wno-strict-aliasing -std=c++11 -fPIC -I$(top_srcdir)/lib -I$(top_srcdir)/rdhpi @QT5_CFLAGS@ @MUSICBRAINZ_CFLAGS@ @IMAGEMAGICK_CFLAGS@
LIBS = -L$(top_srcdir)/lib -L$(top_srcdir)/rdhpi
AM_CPPFLAGS = -Wall -DPREFIX=\"$(prefix)\" -Wno-strict-aliasing -std=c++11 -fPIC -I$(top_srcdir)/lib -I$(top_srcdir)/rdhpi -I$(top_srcdir)/rdalsa @QT5_CFLAGS@ @MUSICBRAINZ_CFLAGS@ @IMAGEMAGICK_CFLAGS@
LIBS = -L$(top_srcdir)/lib -L$(top_srcdir)/rdhpi -L$(top_srcdir)/rdalsa
MOC = @QT_MOC@
# The dependency for qt's Meta Object Compiler (moc)