mirror of
https://github.com/ElvishArtisan/rivendell.git
synced 2025-10-11 09:03:40 +02:00
2021-08-18 Fred Gleason <fredg@paravelsystems.com>
* Refactored the ALSA driver in caed(8) to use virtual inheritance. Signed-off-by: Fred Gleason <fredg@paravelsystems.com>
This commit is contained in:
@@ -22308,3 +22308,5 @@
|
||||
* Refactored the HPI driver in caed(8) to use virtual inheritance.
|
||||
* Disabled JACK driver support in caed(8).
|
||||
* Disabled ALSA driver support in caed(8).
|
||||
2021-08-18 Fred Gleason <fredg@paravelsystems.com>
|
||||
* Refactored the ALSA driver in caed(8) to use virtual inheritance.
|
||||
|
@@ -29,16 +29,16 @@ moc_%.cpp: %.h
|
||||
|
||||
sbin_PROGRAMS = caed
|
||||
|
||||
dist_caed_SOURCES = cae.cpp cae.h\
|
||||
cae_alsa.cpp\
|
||||
cae_hpi.cpp\
|
||||
dist_caed_SOURCES = alsadriver.cpp alsadriver.h\
|
||||
cae.cpp cae.h\
|
||||
cae_jack.cpp\
|
||||
cae_server.cpp cae_server.h\
|
||||
caedriver.cpp caedriver.h\
|
||||
caedriverfactory.cpp caedriverfactory.h\
|
||||
hpidriver.cpp hpidriver.h
|
||||
|
||||
nodist_caed_SOURCES = moc_cae.cpp\
|
||||
nodist_caed_SOURCES = moc_alsadriver.cpp\
|
||||
moc_cae.cpp\
|
||||
moc_cae_server.cpp\
|
||||
moc_caedriver.cpp\
|
||||
moc_hpidriver.cpp
|
||||
|
@@ -1,8 +1,8 @@
|
||||
// cae_alsa.cpp
|
||||
// alsadriver.cpp
|
||||
//
|
||||
// The ALSA Driver for the Core Audio Engine component of Rivendell
|
||||
// caed(8) driver for Advanced Linux Audio Architecture devices
|
||||
//
|
||||
// (C) Copyright 2002-2021 Fred Gleason <fredg@paravelsystems.com>
|
||||
// (C) Copyright 2021 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,20 +18,13 @@
|
||||
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
//
|
||||
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <samplerate.h>
|
||||
|
||||
#include <qsignalmapper.h>
|
||||
|
||||
#include <rd.h>
|
||||
#include <rdapplication.h>
|
||||
#include <rdconf.h>
|
||||
#include <rdmeteraverage.h>
|
||||
#include <rdringbuffer.h>
|
||||
|
||||
#include <cae.h>
|
||||
#include "alsadriver.h"
|
||||
|
||||
#ifdef ALSA
|
||||
//
|
||||
@@ -533,10 +526,10 @@ void *AlsaPlayCallback(void *ptr)
|
||||
}
|
||||
|
||||
|
||||
void MainObject::AlsaInitCallback()
|
||||
void AlsaDriver::AlsaInitCallback()
|
||||
{
|
||||
int avg_periods=
|
||||
(330*system_sample_rate)/(1000*rda->config()->alsaPeriodSize());
|
||||
(330*systemSampleRate())/(1000*rda->config()->alsaPeriodSize());
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
for(int j=0;j<RD_MAX_PORTS;j++) {
|
||||
alsa_recording[i][j]=false;
|
||||
@@ -569,69 +562,10 @@ void MainObject::AlsaInitCallback()
|
||||
#endif // ALSA
|
||||
|
||||
|
||||
void MainObject::alsaStopTimerData(int cardstream)
|
||||
AlsaDriver::AlsaDriver(QObject *parent)
|
||||
: CaeDriver(RDStation::Alsa,parent)
|
||||
{
|
||||
#ifdef ALSA
|
||||
int card=cardstream/RD_MAX_STREAMS;
|
||||
int stream=cardstream-card*RD_MAX_STREAMS;
|
||||
|
||||
alsaStopPlayback(card,stream);
|
||||
statePlayUpdate(card,stream,2);
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
void MainObject::alsaFadeTimerData(int cardstream)
|
||||
{
|
||||
#ifdef ALSA
|
||||
int card=cardstream/RD_MAX_STREAMS;
|
||||
int stream=cardstream-card*RD_MAX_STREAMS;
|
||||
int16_t level;
|
||||
if(alsa_fade_up[card][stream]) {
|
||||
level=alsa_output_volume_db[card][alsa_fade_port[card][stream]][stream]+
|
||||
alsa_fade_increment[card][stream];
|
||||
if(level>=alsa_fade_volume_db[card][stream]) {
|
||||
level=alsa_fade_volume_db[card][stream];
|
||||
alsa_fade_timer[card][stream]->stop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
level=alsa_output_volume_db[card][alsa_fade_port[card][stream]][stream]-
|
||||
alsa_fade_increment[card][stream];
|
||||
if(level<=alsa_fade_volume_db[card][stream]) {
|
||||
level=alsa_fade_volume_db[card][stream];
|
||||
alsa_fade_timer[card][stream]->stop();
|
||||
}
|
||||
}
|
||||
rda->syslog(LOG_DEBUG,"FadeLevel: %d",level);
|
||||
alsaSetOutputVolume(card,stream,alsa_fade_port[card][stream],level);
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
void MainObject::alsaRecordTimerData(int cardport)
|
||||
{
|
||||
#ifdef ALSA
|
||||
int card=cardport/RD_MAX_PORTS;
|
||||
int stream=cardport-card*RD_MAX_PORTS;
|
||||
|
||||
alsaStopRecord(card,stream);
|
||||
stateRecordUpdate(card,stream,2);
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
void MainObject::alsaInit(RDStation *station)
|
||||
{
|
||||
#ifdef ALSA
|
||||
QString dev;
|
||||
int card=0;
|
||||
snd_pcm_t *pcm_play_handle;
|
||||
snd_pcm_t *pcm_capture_handle;
|
||||
snd_ctl_t *snd_ctl;
|
||||
snd_ctl_card_info_t *card_info=NULL;
|
||||
bool pcm_opened;
|
||||
|
||||
//
|
||||
// Initialize Data Structures
|
||||
//
|
||||
@@ -652,18 +586,16 @@ void MainObject::alsaInit(RDStation *station)
|
||||
}
|
||||
}
|
||||
}
|
||||
// alsa_channels=rd_config->channels();
|
||||
|
||||
//
|
||||
// Stop & Fade Timers
|
||||
//
|
||||
QSignalMapper *stop_mapper=new QSignalMapper(this);
|
||||
connect(stop_mapper,SIGNAL(mapped(int)),this,SLOT(alsaStopTimerData(int)));
|
||||
connect(stop_mapper,SIGNAL(mapped(int)),this,SLOT(stopTimerData(int)));
|
||||
QSignalMapper *fade_mapper=new QSignalMapper(this);
|
||||
connect(fade_mapper,SIGNAL(mapped(int)),this,SLOT(alsaFadeTimerData(int)));
|
||||
connect(fade_mapper,SIGNAL(mapped(int)),this,SLOT(fadeTimerData(int)));
|
||||
QSignalMapper *record_mapper=new QSignalMapper(this);
|
||||
connect(record_mapper,SIGNAL(mapped(int)),
|
||||
this,SLOT(alsaRecordTimerData(int)));
|
||||
connect(record_mapper,SIGNAL(mapped(int)),this,SLOT(recordTimerData(int)));
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
for(int j=0;j<RD_MAX_STREAMS;j++) {
|
||||
alsa_stop_timer[i][j]=new QTimer(this);
|
||||
@@ -689,89 +621,25 @@ void MainObject::alsaInit(RDStation *station)
|
||||
AlsaInitCallback();
|
||||
alsa_wave_buffer=new int16_t[RINGBUFFER_SIZE];
|
||||
alsa_wave24_buffer=new uint8_t[2*RINGBUFFER_SIZE];
|
||||
//alsa_resample_buffer=new int16_t[2*RINGBUFFER_SIZE];
|
||||
|
||||
//
|
||||
// Start Up Interfaces
|
||||
//
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
if(cae_driver[i]==RDStation::None) {
|
||||
pcm_opened=false;
|
||||
// These are used to flag bits of card that are not setup
|
||||
// They are cleared just before the pthreads are created.
|
||||
alsa_play_format[i].exiting = true;
|
||||
alsa_capture_format[i].exiting = true;
|
||||
dev=QString().sprintf("rd%d",card);
|
||||
if(snd_pcm_open(&pcm_play_handle,dev.toUtf8(),
|
||||
SND_PCM_STREAM_PLAYBACK,0)==0){
|
||||
pcm_opened=true;
|
||||
if(AlsaStartPlayDevice(dev,i,pcm_play_handle)) {
|
||||
cae_driver[i]=RDStation::Alsa;
|
||||
}
|
||||
else {
|
||||
snd_pcm_close(pcm_play_handle);
|
||||
}
|
||||
}
|
||||
if(snd_pcm_open(&pcm_capture_handle,dev.toUtf8(),
|
||||
SND_PCM_STREAM_CAPTURE,0)==0) {
|
||||
pcm_opened=true;
|
||||
if(AlsaStartCaptureDevice(dev,i,pcm_capture_handle)) {
|
||||
cae_driver[i]=RDStation::Alsa;
|
||||
}
|
||||
else {
|
||||
snd_pcm_close(pcm_capture_handle);
|
||||
}
|
||||
}
|
||||
if(cae_driver[i]==RDStation::Alsa) {
|
||||
station->setCardDriver(i,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());
|
||||
station->setCardName(i,tr("ALSA Device")+" "+dev);
|
||||
}
|
||||
else {
|
||||
snd_ctl_card_info_malloc(&card_info);
|
||||
snd_ctl_card_info(snd_ctl,card_info);
|
||||
station->
|
||||
setCardName(i,snd_ctl_card_info_get_longname(card_info));
|
||||
snd_ctl_close(snd_ctl);
|
||||
}
|
||||
station->
|
||||
setCardInputs(i,
|
||||
alsa_capture_format[i].channels/RD_DEFAULT_CHANNELS);
|
||||
station->
|
||||
setCardOutputs(i,alsa_play_format[i].channels/RD_DEFAULT_CHANNELS);
|
||||
}
|
||||
else {
|
||||
i--;
|
||||
}
|
||||
card++;
|
||||
if(!pcm_opened) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
LoadTwoLame();
|
||||
LoadMad();
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
void MainObject::alsaFree()
|
||||
AlsaDriver::~AlsaDriver()
|
||||
{
|
||||
#ifdef ALSA
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
if(cae_driver[i]==RDStation::Alsa) {
|
||||
if(hasCard(i)) {
|
||||
alsa_play_format[i].exiting=true;
|
||||
pthread_join(alsa_play_format[i].thread,NULL);
|
||||
snd_pcm_close(alsa_play_format[i].pcm);
|
||||
if(alsa_capture_format[i].pcm!=NULL) {
|
||||
printf("SHUTDOWN 1\n");
|
||||
alsa_capture_format[i].exiting=true;
|
||||
printf("SHUTDOWN 2\n");
|
||||
pthread_join(alsa_capture_format[i].thread,NULL);
|
||||
printf("SHUTDOWN 3\n");
|
||||
snd_pcm_close(alsa_capture_format[i].pcm);
|
||||
printf("SHUTDOWN 4\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -779,20 +647,94 @@ void MainObject::alsaFree()
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaLoadPlayback(int card,QString wavename,int *stream)
|
||||
QString AlsaDriver::version() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
bool AlsaDriver::initialize(unsigned *next_cardnum)
|
||||
{
|
||||
QString dev;
|
||||
snd_pcm_t *pcm_play_handle;
|
||||
snd_pcm_t *pcm_capture_handle;
|
||||
snd_ctl_t *snd_ctl;
|
||||
snd_ctl_card_info_t *card_info=NULL;
|
||||
bool pcm_opened;
|
||||
int card=0;
|
||||
|
||||
//
|
||||
// Start Up Interfaces
|
||||
//
|
||||
while((*next_cardnum)<RD_MAX_CARDS) {
|
||||
pcm_opened=false;
|
||||
alsa_play_format[*next_cardnum].exiting = true;
|
||||
alsa_capture_format[*next_cardnum].exiting = true;
|
||||
dev=QString().sprintf("rd%d",card);
|
||||
if(snd_pcm_open(&pcm_play_handle,dev.toUtf8(),
|
||||
SND_PCM_STREAM_PLAYBACK,0)==0){
|
||||
pcm_opened=true;
|
||||
if(!AlsaStartPlayDevice(dev,*next_cardnum,pcm_play_handle)) {
|
||||
snd_pcm_close(pcm_play_handle);
|
||||
}
|
||||
}
|
||||
if(snd_pcm_open(&pcm_capture_handle,dev.toUtf8(),
|
||||
SND_PCM_STREAM_CAPTURE,0)==0) {
|
||||
pcm_opened=true;
|
||||
if(!AlsaStartCaptureDevice(dev,*next_cardnum,pcm_capture_handle)) {
|
||||
snd_pcm_close(pcm_capture_handle);
|
||||
}
|
||||
}
|
||||
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()->
|
||||
setCardInputs(*next_cardnum,
|
||||
alsa_capture_format[*next_cardnum].
|
||||
channels/RD_DEFAULT_CHANNELS);
|
||||
rda->station()->
|
||||
setCardOutputs(*next_cardnum,
|
||||
alsa_play_format[*next_cardnum].
|
||||
channels/RD_DEFAULT_CHANNELS);
|
||||
card++;
|
||||
if(!pcm_opened) {
|
||||
return card>0;
|
||||
}
|
||||
addCard(*next_cardnum);
|
||||
(*next_cardnum)++;
|
||||
}
|
||||
return card>0;
|
||||
}
|
||||
|
||||
|
||||
void AlsaDriver::updateMeters()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool AlsaDriver::loadPlayback(int card,QString wavename,int *stream)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if(alsa_play_format[card].exiting||((*stream=GetAlsaOutputStream(card))<0)) {
|
||||
rda->syslog(LOG_DEBUG,
|
||||
"alsaLoadPlayback(%s) GetAlsaOutputStream():%d < 0",
|
||||
(const char *)wavename.toUtf8(),*stream);
|
||||
rda->syslog(LOG_DEBUG,"alsaLoadPlayback(%s) GetAlsaOutputStream():%d < 0",
|
||||
wavename.toUtf8().constData(),*stream);
|
||||
return false;
|
||||
}
|
||||
alsa_play_wave[card][*stream]=new RDWaveFile(wavename);
|
||||
if(!alsa_play_wave[card][*stream]->openWave()) {
|
||||
rda->syslog(LOG_DEBUG,
|
||||
"alsaLoadPlayback(%s) openWave() failed to open file",
|
||||
(const char *)wavename.toUtf8());
|
||||
rda->syslog(LOG_DEBUG,"alsaLoadPlayback(%s) openWave() failed to open file",
|
||||
wavename.toUtf8().constData());
|
||||
delete alsa_play_wave[card][*stream];
|
||||
alsa_play_wave[card][*stream]=NULL;
|
||||
FreeAlsaOutputStream(card,*stream);
|
||||
@@ -805,13 +747,19 @@ bool MainObject::alsaLoadPlayback(int card,QString wavename,int *stream)
|
||||
break;
|
||||
|
||||
case WAVE_FORMAT_MPEG:
|
||||
InitMadDecoder(card,*stream,alsa_play_wave[card][*stream]);
|
||||
if(!InitMadDecoder(card,*stream,alsa_play_wave[card][*stream])) {
|
||||
delete alsa_play_wave[card][*stream];
|
||||
alsa_play_wave[card][*stream]=NULL;
|
||||
FreeAlsaOutputStream(card,*stream);
|
||||
*stream=-1;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
rda->syslog(LOG_WARNING,
|
||||
"alsaLoadPlayback(%s) getFormatTag()%d || getBistsPerSample()%d failed",
|
||||
(const char *)wavename.toUtf8(),
|
||||
wavename.toUtf8().constData(),
|
||||
alsa_play_wave[card][*stream]->getFormatTag(),
|
||||
alsa_play_wave[card][*stream]->getBitsPerSample());
|
||||
delete alsa_play_wave[card][*stream];
|
||||
@@ -835,7 +783,7 @@ bool MainObject::alsaLoadPlayback(int card,QString wavename,int *stream)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaUnloadPlayback(int card,int stream)
|
||||
bool AlsaDriver::unloadPlayback(int card,int stream)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if(alsa_play_ring[card][stream]==NULL) {
|
||||
@@ -858,7 +806,7 @@ bool MainObject::alsaUnloadPlayback(int card,int stream)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaPlaybackPosition(int card,int stream,unsigned pos)
|
||||
bool AlsaDriver::playbackPosition(int card,int stream,unsigned pos)
|
||||
{
|
||||
#ifdef ALSA
|
||||
unsigned offset=0;
|
||||
@@ -909,8 +857,8 @@ bool MainObject::alsaPlaybackPosition(int card,int stream,unsigned pos)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaPlay(int card,int stream,int length,int speed,bool pitch,
|
||||
bool rates)
|
||||
bool AlsaDriver::play(int card,int stream,int length,int speed,bool pitch,
|
||||
bool rates)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if((alsa_play_ring[card][stream]==NULL)||
|
||||
@@ -929,17 +877,7 @@ bool MainObject::alsaPlay(int card,int stream,int length,int speed,bool pitch,
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaTimescaleSupported(int card)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaStopPlayback(int card,int stream)
|
||||
bool AlsaDriver::stopPlayback(int card,int stream)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if((alsa_play_ring[card][stream]==NULL)||(!alsa_playing[card][stream])) {
|
||||
@@ -956,95 +894,101 @@ bool MainObject::alsaStopPlayback(int card,int stream)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaLoadRecord(int card,int stream,int coding,int chans,
|
||||
int samprate,int bitrate,QString wavename)
|
||||
bool AlsaDriver::timescaleSupported(int card)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool AlsaDriver::loadRecord(int card,int port,int coding,int chans,int samprate,
|
||||
int bitrate,QString wavename)
|
||||
{
|
||||
#ifdef ALSA
|
||||
alsa_record_wave[card][stream]=new RDWaveFile(wavename);
|
||||
alsa_record_wave[card][port]=new RDWaveFile(wavename);
|
||||
switch(coding) {
|
||||
case 0: // PCM16
|
||||
alsa_record_wave[card][stream]->setFormatTag(WAVE_FORMAT_PCM);
|
||||
alsa_record_wave[card][stream]->setChannels(chans);
|
||||
alsa_record_wave[card][stream]->setSamplesPerSec(samprate);
|
||||
alsa_record_wave[card][stream]->setBitsPerSample(16);
|
||||
alsa_record_wave[card][port]->setFormatTag(WAVE_FORMAT_PCM);
|
||||
alsa_record_wave[card][port]->setChannels(chans);
|
||||
alsa_record_wave[card][port]->setSamplesPerSec(samprate);
|
||||
alsa_record_wave[card][port]->setBitsPerSample(16);
|
||||
break;
|
||||
|
||||
case 4: // PCM24
|
||||
alsa_record_wave[card][stream]->setFormatTag(WAVE_FORMAT_PCM);
|
||||
alsa_record_wave[card][stream]->setChannels(chans);
|
||||
alsa_record_wave[card][stream]->setSamplesPerSec(samprate);
|
||||
alsa_record_wave[card][stream]->setBitsPerSample(24);
|
||||
alsa_record_wave[card][port]->setFormatTag(WAVE_FORMAT_PCM);
|
||||
alsa_record_wave[card][port]->setChannels(chans);
|
||||
alsa_record_wave[card][port]->setSamplesPerSec(samprate);
|
||||
alsa_record_wave[card][port]->setBitsPerSample(24);
|
||||
break;
|
||||
|
||||
case 2: // MPEG Layer 2
|
||||
if(!InitTwoLameEncoder(card,stream,chans,samprate,bitrate)) {
|
||||
delete alsa_record_wave[card][stream];
|
||||
alsa_record_wave[card][stream]=NULL;
|
||||
if(!InitTwoLameEncoder(card,port,chans,samprate,bitrate)) {
|
||||
delete alsa_record_wave[card][port];
|
||||
alsa_record_wave[card][port]=NULL;
|
||||
return false;
|
||||
}
|
||||
alsa_record_wave[card][stream]->setFormatTag(WAVE_FORMAT_MPEG);
|
||||
alsa_record_wave[card][stream]->setChannels(chans);
|
||||
alsa_record_wave[card][stream]->setSamplesPerSec(samprate);
|
||||
alsa_record_wave[card][stream]->setBitsPerSample(16);
|
||||
alsa_record_wave[card][stream]->setHeadLayer(ACM_MPEG_LAYER2);
|
||||
alsa_record_wave[card][port]->setFormatTag(WAVE_FORMAT_MPEG);
|
||||
alsa_record_wave[card][port]->setChannels(chans);
|
||||
alsa_record_wave[card][port]->setSamplesPerSec(samprate);
|
||||
alsa_record_wave[card][port]->setBitsPerSample(16);
|
||||
alsa_record_wave[card][port]->setHeadLayer(ACM_MPEG_LAYER2);
|
||||
switch(chans) {
|
||||
case 1:
|
||||
alsa_record_wave[card][stream]->setHeadMode(ACM_MPEG_SINGLECHANNEL);
|
||||
alsa_record_wave[card][port]->setHeadMode(ACM_MPEG_SINGLECHANNEL);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
alsa_record_wave[card][stream]->setHeadMode(ACM_MPEG_STEREO);
|
||||
alsa_record_wave[card][port]->setHeadMode(ACM_MPEG_STEREO);
|
||||
break;
|
||||
|
||||
default:
|
||||
rda->syslog(LOG_WARNING,
|
||||
"requested unsupported channel count %d, card: %d, stream: %d",
|
||||
chans,card,stream);
|
||||
delete alsa_record_wave[card][stream];
|
||||
alsa_record_wave[card][stream]=NULL;
|
||||
"requested unsupported channel count %d, card: %d, port: %d",
|
||||
chans,card,port);
|
||||
delete alsa_record_wave[card][port];
|
||||
alsa_record_wave[card][port]=NULL;
|
||||
return false;
|
||||
}
|
||||
alsa_record_wave[card][stream]->setHeadBitRate(bitrate);
|
||||
alsa_record_wave[card][stream]->setMextChunk(true);
|
||||
alsa_record_wave[card][stream]->setMextHomogenous(true);
|
||||
alsa_record_wave[card][stream]->setMextPaddingUsed(false);
|
||||
alsa_record_wave[card][stream]->setMextHackedBitRate(true);
|
||||
alsa_record_wave[card][stream]->setMextFreeFormat(false);
|
||||
alsa_record_wave[card][stream]->
|
||||
setMextFrameSize(144*alsa_record_wave[card][stream]->getHeadBitRate()/
|
||||
alsa_record_wave[card][stream]->getSamplesPerSec());
|
||||
alsa_record_wave[card][stream]->setMextAncillaryLength(5);
|
||||
alsa_record_wave[card][stream]->setMextLeftEnergyPresent(true);
|
||||
alsa_record_wave[card][port]->setHeadBitRate(bitrate);
|
||||
alsa_record_wave[card][port]->setMextChunk(true);
|
||||
alsa_record_wave[card][port]->setMextHomogenous(true);
|
||||
alsa_record_wave[card][port]->setMextPaddingUsed(false);
|
||||
alsa_record_wave[card][port]->setMextHackedBitRate(true);
|
||||
alsa_record_wave[card][port]->setMextFreeFormat(false);
|
||||
alsa_record_wave[card][port]->
|
||||
setMextFrameSize(144*alsa_record_wave[card][port]->getHeadBitRate()/
|
||||
alsa_record_wave[card][port]->getSamplesPerSec());
|
||||
alsa_record_wave[card][port]->setMextAncillaryLength(5);
|
||||
alsa_record_wave[card][port]->setMextLeftEnergyPresent(true);
|
||||
if(chans>1) {
|
||||
alsa_record_wave[card][stream]->setMextRightEnergyPresent(true);
|
||||
alsa_record_wave[card][port]->setMextRightEnergyPresent(true);
|
||||
}
|
||||
else {
|
||||
alsa_record_wave[card][stream]->setMextRightEnergyPresent(false);
|
||||
alsa_record_wave[card][port]->setMextRightEnergyPresent(false);
|
||||
}
|
||||
alsa_record_wave[card][stream]->setMextPrivateDataPresent(false);
|
||||
alsa_record_wave[card][port]->setMextPrivateDataPresent(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
rda->syslog(LOG_WARNING,
|
||||
"requested invalid audio encoding %d, card: %d, stream: %d",
|
||||
coding,card,stream);
|
||||
delete alsa_record_wave[card][stream];
|
||||
alsa_record_wave[card][stream]=NULL;
|
||||
"requested invalid audio encoding %d, card: %d, port: %d",
|
||||
coding,card,port);
|
||||
delete alsa_record_wave[card][port];
|
||||
alsa_record_wave[card][port]=NULL;
|
||||
return false;
|
||||
}
|
||||
alsa_record_wave[card][stream]->setBextChunk(true);
|
||||
alsa_record_wave[card][stream]->setLevlChunk(true);
|
||||
if(!alsa_record_wave[card][stream]->createWave()) {
|
||||
delete alsa_record_wave[card][stream];
|
||||
alsa_record_wave[card][stream]=NULL;
|
||||
alsa_record_wave[card][port]->setBextChunk(true);
|
||||
alsa_record_wave[card][port]->setLevlChunk(true);
|
||||
if(!alsa_record_wave[card][port]->createWave()) {
|
||||
delete alsa_record_wave[card][port];
|
||||
alsa_record_wave[card][port]=NULL;
|
||||
return false;
|
||||
}
|
||||
RDCheckExitCode(rda->config(),"alsaLoadRecord() chown",
|
||||
chown(wavename.toUtf8(),rda->config()->uid(),rda->config()->gid()));
|
||||
alsa_input_channels[card][stream]=chans;
|
||||
alsa_record_ring[card][stream]=new RDRingBuffer(RINGBUFFER_SIZE);
|
||||
alsa_record_ring[card][stream]->reset();
|
||||
alsa_ready[card][stream]=true;
|
||||
alsa_input_channels[card][port]=chans;
|
||||
alsa_record_ring[card][port]=new RDRingBuffer(RINGBUFFER_SIZE);
|
||||
alsa_record_ring[card][port]->reset();
|
||||
alsa_ready[card][port]=true;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
@@ -1052,20 +996,20 @@ bool MainObject::alsaLoadRecord(int card,int stream,int coding,int chans,
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaUnloadRecord(int card,int stream,unsigned *len)
|
||||
bool AlsaDriver::unloadRecord(int card,int port,unsigned *len)
|
||||
{
|
||||
#ifdef ALSA
|
||||
alsa_recording[card][stream]=false;
|
||||
alsa_ready[card][stream]=false;
|
||||
EmptyAlsaInputStream(card,stream);
|
||||
*len=alsa_samples_recorded[card][stream];
|
||||
alsa_samples_recorded[card][stream]=0;
|
||||
alsa_record_wave[card][stream]->closeWave(*len);
|
||||
delete alsa_record_wave[card][stream];
|
||||
alsa_record_wave[card][stream]=NULL;
|
||||
delete alsa_record_ring[card][stream];
|
||||
alsa_record_ring[card][stream]=NULL;
|
||||
FreeTwoLameEncoder(card,stream);
|
||||
alsa_recording[card][port]=false;
|
||||
alsa_ready[card][port]=false;
|
||||
EmptyAlsaInputStream(card,port);
|
||||
*len=alsa_samples_recorded[card][port];
|
||||
alsa_samples_recorded[card][port]=0;
|
||||
alsa_record_wave[card][port]->closeWave(*len);
|
||||
delete alsa_record_wave[card][port];
|
||||
alsa_record_wave[card][port]=NULL;
|
||||
delete alsa_record_ring[card][port];
|
||||
alsa_record_ring[card][port]=NULL;
|
||||
FreeTwoLameEncoder(card,port);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
@@ -1073,18 +1017,18 @@ bool MainObject::alsaUnloadRecord(int card,int stream,unsigned *len)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaRecord(int card,int stream,int length,int thres)
|
||||
bool AlsaDriver::record(int card,int port,int length,int thres)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if(!alsa_ready[card][stream]) {
|
||||
if(!alsa_ready[card][port]) {
|
||||
return false;
|
||||
}
|
||||
alsa_recording[card][stream]=true;
|
||||
if(alsa_input_vox[card][stream]==0.0) {
|
||||
alsa_recording[card][port]=true;
|
||||
if(alsa_input_vox[card][port]==0.0) {
|
||||
if(length>0) {
|
||||
alsa_record_timer[card][stream]->start(length);
|
||||
alsa_record_timer[card][port]->start(length);
|
||||
}
|
||||
stateRecordUpdate(card,stream,4);
|
||||
stateRecordUpdate(card,port,4);
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
@@ -1093,13 +1037,13 @@ bool MainObject::alsaRecord(int card,int stream,int length,int thres)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaStopRecord(int card,int stream)
|
||||
bool AlsaDriver::stopRecord(int card,int port)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if(!alsa_recording[card][stream]) {
|
||||
if(!alsa_recording[card][port]) {
|
||||
return false;
|
||||
}
|
||||
alsa_recording[card][stream]=false;
|
||||
alsa_recording[card][port]=false;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
@@ -1107,7 +1051,17 @@ bool MainObject::alsaStopRecord(int card,int stream)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetInputVolume(int card,int stream,int level)
|
||||
bool AlsaDriver::setClockSource(int card,int src)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
bool AlsaDriver::setInputVolume(int card,int stream,int level)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if(level>-10000) {
|
||||
@@ -1125,7 +1079,7 @@ bool MainObject::alsaSetInputVolume(int card,int stream,int level)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetOutputVolume(int card,int stream,int port,int level)
|
||||
bool AlsaDriver::setOutputVolume(int card,int stream,int port,int level)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if(level>-10000) {
|
||||
@@ -1143,8 +1097,8 @@ bool MainObject::alsaSetOutputVolume(int card,int stream,int port,int level)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaFadeOutputVolume(int card,int stream,int port,int level,
|
||||
int length)
|
||||
bool AlsaDriver::fadeOutputVolume(int card,int stream,int port,int level,
|
||||
int length)
|
||||
{
|
||||
#ifdef ALSA
|
||||
int diff;
|
||||
@@ -1171,7 +1125,7 @@ bool MainObject::alsaFadeOutputVolume(int card,int stream,int port,int level,
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetInputLevel(int card,int port,int level)
|
||||
bool AlsaDriver::setInputLevel(int card,int port,int level)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return true;
|
||||
@@ -1181,7 +1135,7 @@ bool MainObject::alsaSetInputLevel(int card,int port,int level)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetOutputLevel(int card,int port,int level)
|
||||
bool AlsaDriver::setOutputLevel(int card,int port,int level)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return true;
|
||||
@@ -1191,7 +1145,7 @@ bool MainObject::alsaSetOutputLevel(int card,int port,int level)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetInputMode(int card,int stream,int mode)
|
||||
bool AlsaDriver::setInputMode(int card,int stream,int mode)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return true;
|
||||
@@ -1201,7 +1155,7 @@ bool MainObject::alsaSetInputMode(int card,int stream,int mode)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetOutputMode(int card,int stream,int mode)
|
||||
bool AlsaDriver::setOutputMode(int card,int stream,int mode)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return true;
|
||||
@@ -1211,7 +1165,7 @@ bool MainObject::alsaSetOutputMode(int card,int stream,int mode)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetInputVoxLevel(int card,int stream,int level)
|
||||
bool AlsaDriver::setInputVoxLevel(int card,int stream,int level)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return true;
|
||||
@@ -1221,7 +1175,7 @@ bool MainObject::alsaSetInputVoxLevel(int card,int stream,int level)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetInputType(int card,int port,int type)
|
||||
bool AlsaDriver::setInputType(int card,int port,int type)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return true;
|
||||
@@ -1231,7 +1185,7 @@ bool MainObject::alsaSetInputType(int card,int port,int type)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaGetInputStatus(int card,int port)
|
||||
bool AlsaDriver::getInputStatus(int card,int port)
|
||||
{
|
||||
#ifdef ALSA
|
||||
return true;
|
||||
@@ -1241,7 +1195,7 @@ bool MainObject::alsaGetInputStatus(int card,int port)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaGetInputMeters(int card,int port,int16_t levels[2])
|
||||
bool AlsaDriver::getInputMeters(int card,int port,short levels[2])
|
||||
{
|
||||
#ifdef ALSA
|
||||
double meter;
|
||||
@@ -1266,7 +1220,7 @@ bool MainObject::alsaGetInputMeters(int card,int port,int16_t levels[2])
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaGetOutputMeters(int card,int port,int16_t levels[2])
|
||||
bool AlsaDriver::getOutputMeters(int card,int port,short levels[2])
|
||||
{
|
||||
#ifdef ALSA
|
||||
double meter;
|
||||
@@ -1290,7 +1244,7 @@ bool MainObject::alsaGetOutputMeters(int card,int port,int16_t levels[2])
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaGetStreamOutputMeters(int card,int stream,int16_t levels[2])
|
||||
bool AlsaDriver::getStreamOutputMeters(int card,int stream,short levels[2])
|
||||
{
|
||||
#ifdef ALSA
|
||||
double meter;
|
||||
@@ -1314,25 +1268,8 @@ bool MainObject::alsaGetStreamOutputMeters(int card,int stream,int16_t levels[2]
|
||||
}
|
||||
|
||||
|
||||
void MainObject::alsaGetOutputPosition(int card,unsigned *pos)
|
||||
{// pos is in miliseconds
|
||||
#ifdef ALSA
|
||||
for(int i=0;i<RD_MAX_STREAMS;i++) {
|
||||
if((!alsa_play_format[card].exiting)&&(alsa_play_wave[card][i]!=NULL)) {
|
||||
pos[i]=1000*(unsigned long long)(alsa_offset[card][i]+
|
||||
alsa_output_pos[card][i])/
|
||||
alsa_play_wave[card][i]->getSamplesPerSec();
|
||||
}
|
||||
else {
|
||||
pos[i]=0;
|
||||
}
|
||||
}
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::alsaSetPassthroughLevel(int card,int in_port,int out_port,
|
||||
int level)
|
||||
bool AlsaDriver::setPassthroughLevel(int card,int in_port,int out_port,
|
||||
int level)
|
||||
{
|
||||
#ifdef ALSA
|
||||
if(level>-10000) {
|
||||
@@ -1351,8 +1288,104 @@ bool MainObject::alsaSetPassthroughLevel(int card,int in_port,int out_port,
|
||||
}
|
||||
|
||||
|
||||
void AlsaDriver::getOutputPosition(int card,unsigned *pos)
|
||||
{// pos is in miliseconds
|
||||
#ifdef ALSA
|
||||
bool MainObject::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm)
|
||||
for(int i=0;i<RD_MAX_STREAMS;i++) {
|
||||
if((!alsa_play_format[card].exiting)&&(alsa_play_wave[card][i]!=NULL)) {
|
||||
pos[i]=1000*(unsigned long long)(alsa_offset[card][i]+
|
||||
alsa_output_pos[card][i])/
|
||||
alsa_play_wave[card][i]->getSamplesPerSec();
|
||||
}
|
||||
else {
|
||||
pos[i]=0;
|
||||
}
|
||||
}
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
void AlsaDriver::processBuffers()
|
||||
{
|
||||
#ifdef ALSA
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
if(hasCard(i)) {
|
||||
for(int j=0;j<RD_MAX_STREAMS;j++) {
|
||||
if(alsa_stopping[i][j]) {
|
||||
alsa_stopping[i][j]=false;
|
||||
alsa_eof[i][j]=false;
|
||||
alsa_playing[i][j]=false;
|
||||
printf("stop card: %d stream: %d\n",i,j);
|
||||
statePlayUpdate(i,j,2);
|
||||
}
|
||||
if(alsa_playing[i][j]) {
|
||||
FillAlsaOutputStream(i,j);
|
||||
}
|
||||
}
|
||||
for(int j=0;j<RD_MAX_PORTS;j++) {
|
||||
if(alsa_recording[i][j]) {
|
||||
EmptyAlsaInputStream(i,j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
void AlsaDriver::stopTimerData(int cardstream)
|
||||
{
|
||||
#ifdef ALSA
|
||||
int card=cardstream/RD_MAX_STREAMS;
|
||||
int stream=cardstream-card*RD_MAX_STREAMS;
|
||||
|
||||
stopPlayback(card,stream);
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
void AlsaDriver::fadeTimerData(int cardstream)
|
||||
{
|
||||
#ifdef ALSA
|
||||
int card=cardstream/RD_MAX_STREAMS;
|
||||
int stream=cardstream-card*RD_MAX_STREAMS;
|
||||
int16_t level;
|
||||
if(alsa_fade_up[card][stream]) {
|
||||
level=alsa_output_volume_db[card][alsa_fade_port[card][stream]][stream]+
|
||||
alsa_fade_increment[card][stream];
|
||||
if(level>=alsa_fade_volume_db[card][stream]) {
|
||||
level=alsa_fade_volume_db[card][stream];
|
||||
alsa_fade_timer[card][stream]->stop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
level=alsa_output_volume_db[card][alsa_fade_port[card][stream]][stream]-
|
||||
alsa_fade_increment[card][stream];
|
||||
if(level<=alsa_fade_volume_db[card][stream]) {
|
||||
level=alsa_fade_volume_db[card][stream];
|
||||
alsa_fade_timer[card][stream]->stop();
|
||||
}
|
||||
}
|
||||
rda->syslog(LOG_DEBUG,"FadeLevel: %d",level);
|
||||
setOutputVolume(card,stream,alsa_fade_port[card][stream],level);
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
void AlsaDriver::recordTimerData(int cardport)
|
||||
{
|
||||
#ifdef ALSA
|
||||
int card=cardport/RD_MAX_PORTS;
|
||||
int stream=cardport-card*RD_MAX_PORTS;
|
||||
|
||||
stopRecord(card,stream);
|
||||
stateRecordUpdate(card,stream,2);
|
||||
#endif // ALSA
|
||||
}
|
||||
|
||||
|
||||
#ifdef ALSA
|
||||
bool AlsaDriver::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm)
|
||||
{
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
@@ -1412,14 +1445,14 @@ bool MainObject::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm)
|
||||
sr=alsa_play_format[card].sample_rate;
|
||||
}
|
||||
else {
|
||||
sr=system_sample_rate;
|
||||
sr=systemSampleRate();
|
||||
}
|
||||
snd_pcm_hw_params_set_rate_near(pcm,hwparams,&sr,&dir);
|
||||
if((sr<(system_sample_rate-RD_ALSA_SAMPLE_RATE_TOLERANCE))||
|
||||
(sr>(system_sample_rate+RD_ALSA_SAMPLE_RATE_TOLERANCE))) {
|
||||
if((sr<(systemSampleRate()-RD_ALSA_SAMPLE_RATE_TOLERANCE))||
|
||||
(sr>(systemSampleRate()+RD_ALSA_SAMPLE_RATE_TOLERANCE))) {
|
||||
rda->syslog(LOG_WARNING,
|
||||
" Asked for sample rate %u, got %u",
|
||||
system_sample_rate,sr);
|
||||
systemSampleRate(),sr);
|
||||
rda->syslog(LOG_WARNING,
|
||||
" Sample rate unsupported by device");
|
||||
return false;
|
||||
@@ -1519,7 +1552,7 @@ bool MainObject::AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm)
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm)
|
||||
bool AlsaDriver::AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm)
|
||||
{
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
@@ -1575,13 +1608,13 @@ bool MainObject::AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm)
|
||||
//
|
||||
// Sample Rate
|
||||
//
|
||||
sr=system_sample_rate;
|
||||
sr=systemSampleRate();
|
||||
snd_pcm_hw_params_set_rate_near(pcm,hwparams,&sr,&dir);
|
||||
if((sr<(system_sample_rate-RD_ALSA_SAMPLE_RATE_TOLERANCE))||
|
||||
(sr>(system_sample_rate+RD_ALSA_SAMPLE_RATE_TOLERANCE))) {
|
||||
if((sr<(systemSampleRate()-RD_ALSA_SAMPLE_RATE_TOLERANCE))||
|
||||
(sr>(systemSampleRate()+RD_ALSA_SAMPLE_RATE_TOLERANCE))) {
|
||||
rda->syslog(LOG_WARNING,
|
||||
" Asked for sample rate %u, got %u",
|
||||
system_sample_rate,sr);
|
||||
systemSampleRate(),sr);
|
||||
rda->syslog(LOG_WARNING,
|
||||
" Sample rate unsupported by device");
|
||||
return false;
|
||||
@@ -1677,7 +1710,7 @@ bool MainObject::AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm)
|
||||
}
|
||||
|
||||
|
||||
int MainObject::GetAlsaOutputStream(int card)
|
||||
int AlsaDriver::GetAlsaOutputStream(int card)
|
||||
{
|
||||
for(int i=0;i<RD_MAX_STREAMS;i++) {
|
||||
if(alsa_play_ring[card][i]==NULL) {
|
||||
@@ -1689,14 +1722,14 @@ int MainObject::GetAlsaOutputStream(int card)
|
||||
}
|
||||
|
||||
|
||||
void MainObject::FreeAlsaOutputStream(int card,int stream)
|
||||
void AlsaDriver::FreeAlsaOutputStream(int card,int stream)
|
||||
{
|
||||
delete alsa_play_ring[card][stream];
|
||||
alsa_play_ring[card][stream]=NULL;
|
||||
}
|
||||
|
||||
|
||||
void MainObject::EmptyAlsaInputStream(int card,int stream)
|
||||
void AlsaDriver::EmptyAlsaInputStream(int card,int stream)
|
||||
{
|
||||
unsigned n=alsa_record_ring[card][stream]->
|
||||
read((char *)alsa_wave_buffer,alsa_record_ring[card][stream]->
|
||||
@@ -1705,7 +1738,7 @@ void MainObject::EmptyAlsaInputStream(int card,int stream)
|
||||
}
|
||||
|
||||
|
||||
void MainObject::WriteAlsaBuffer(int card,int stream,int16_t *buffer,unsigned len)
|
||||
void AlsaDriver::WriteAlsaBuffer(int card,int stream,int16_t *buffer,unsigned len)
|
||||
{
|
||||
ssize_t s;
|
||||
unsigned char mpeg[2048];
|
||||
@@ -1758,7 +1791,7 @@ void MainObject::WriteAlsaBuffer(int card,int stream,int16_t *buffer,unsigned le
|
||||
}
|
||||
|
||||
|
||||
void MainObject::FillAlsaOutputStream(int card,int stream)
|
||||
void AlsaDriver::FillAlsaOutputStream(int card,int stream)
|
||||
{
|
||||
unsigned mpeg_frames=0;
|
||||
unsigned frame_offset=0;
|
||||
@@ -1869,11 +1902,11 @@ void MainObject::FillAlsaOutputStream(int card,int stream)
|
||||
#endif // ALSA
|
||||
|
||||
|
||||
void MainObject::AlsaClock()
|
||||
void AlsaDriver::AlsaClock()
|
||||
{
|
||||
#ifdef ALSA
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
if(cae_driver[i]==RDStation::Alsa) {
|
||||
if(hasCard(i)) {
|
||||
for(int j=0;j<RD_MAX_STREAMS;j++) {
|
||||
if(alsa_stopping[i][j]) {
|
||||
alsa_stopping[i][j]=false;
|
129
cae/alsadriver.h
Normal file
129
cae/alsadriver.h
Normal file
@@ -0,0 +1,129 @@
|
||||
// alsadriver.h
|
||||
//
|
||||
// caed(8) driver for Advanced Linux Audio Architecture devices
|
||||
//
|
||||
// (C) Copyright 2021 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.
|
||||
//
|
||||
|
||||
#ifndef ALSADRIVER_H
|
||||
#define ALSADRIVER_H
|
||||
|
||||
#include <rdconfig.h>
|
||||
#include <rdwavefile.h>
|
||||
|
||||
#include "caedriver.h"
|
||||
|
||||
#ifdef ALSA
|
||||
#include <alsa/asoundlib.h>
|
||||
struct alsa_format {
|
||||
int card;
|
||||
pthread_t thread;
|
||||
snd_pcm_t *pcm;
|
||||
unsigned channels;
|
||||
unsigned capture_channels;
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
snd_pcm_format_t format;
|
||||
unsigned sample_rate;
|
||||
char *card_buffer;
|
||||
char *passthrough_buffer;
|
||||
unsigned card_buffer_size;
|
||||
unsigned periods;
|
||||
bool exiting;
|
||||
};
|
||||
#endif // ALSA
|
||||
|
||||
class AlsaDriver : public CaeDriver
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AlsaDriver(QObject *parent=0);
|
||||
~AlsaDriver();
|
||||
QString version() const;
|
||||
bool initialize(unsigned *next_cardnum);
|
||||
void updateMeters();
|
||||
bool loadPlayback(int card,QString wavename,int *stream);
|
||||
bool unloadPlayback(int card,int stream);
|
||||
bool playbackPosition(int card,int stream,unsigned pos);
|
||||
bool play(int card,int stream,int length,int speed,bool pitch,
|
||||
bool rates);
|
||||
bool stopPlayback(int card,int stream);
|
||||
bool timescaleSupported(int card);
|
||||
bool loadRecord(int card,int port,int coding,int chans,int samprate,
|
||||
int bitrate,QString wavename);
|
||||
bool unloadRecord(int card,int port,unsigned *len);
|
||||
bool record(int card,int port,int length,int thres);
|
||||
bool stopRecord(int card,int port);
|
||||
bool setClockSource(int card,int src);
|
||||
bool setInputVolume(int card,int stream,int level);
|
||||
bool setOutputVolume(int card,int stream,int port,int level);
|
||||
bool fadeOutputVolume(int card,int stream,int port,int level,
|
||||
int length);
|
||||
bool setInputLevel(int card,int port,int level);
|
||||
bool setOutputLevel(int card,int port,int level);
|
||||
bool setInputMode(int card,int stream,int mode);
|
||||
bool setOutputMode(int card,int stream,int mode);
|
||||
bool setInputVoxLevel(int card,int stream,int level);
|
||||
bool setInputType(int card,int port,int type);
|
||||
bool getInputStatus(int card,int port);
|
||||
bool getInputMeters(int card,int port,short levels[2]);
|
||||
bool getOutputMeters(int card,int port,short levels[2]);
|
||||
bool getStreamOutputMeters(int card,int stream,short levels[2]);
|
||||
bool setPassthroughLevel(int card,int in_port,int out_port,
|
||||
int level);
|
||||
void getOutputPosition(int card,unsigned *pos);
|
||||
|
||||
public slots:
|
||||
void processBuffers();
|
||||
|
||||
private slots:
|
||||
void stopTimerData(int cardstream);
|
||||
void fadeTimerData(int cardstream);
|
||||
void recordTimerData(int cardport);
|
||||
|
||||
private:
|
||||
#ifdef ALSA
|
||||
bool AlsaStartCaptureDevice(QString &dev,int card,snd_pcm_t *pcm);
|
||||
bool AlsaStartPlayDevice(QString &dev,int card,snd_pcm_t *pcm);
|
||||
void AlsaInitCallback();
|
||||
int GetAlsaOutputStream(int card);
|
||||
void FreeAlsaOutputStream(int card,int stream);
|
||||
void EmptyAlsaInputStream(int card,int stream);
|
||||
void WriteAlsaBuffer(int card,int stream,short *buffer,unsigned len);
|
||||
void FillAlsaOutputStream(int card,int stream);
|
||||
void AlsaClock();
|
||||
struct alsa_format alsa_play_format[RD_MAX_CARDS];
|
||||
struct alsa_format alsa_capture_format[RD_MAX_CARDS];
|
||||
short alsa_input_volume_db[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
short alsa_output_volume_db[RD_MAX_CARDS][RD_MAX_PORTS][RD_MAX_STREAMS];
|
||||
short alsa_passthrough_volume_db[RD_MAX_CARDS][RD_MAX_PORTS][RD_MAX_PORTS];
|
||||
short *alsa_wave_buffer;
|
||||
uint8_t *alsa_wave24_buffer;
|
||||
RDWaveFile *alsa_record_wave[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
RDWaveFile *alsa_play_wave[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
int alsa_offset[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
QTimer *alsa_fade_timer[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
QTimer *alsa_stop_timer[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
QTimer *alsa_record_timer[RD_MAX_CARDS][RD_MAX_PORTS];
|
||||
bool alsa_fade_up[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
short alsa_fade_volume_db[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
short alsa_fade_increment[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
int alsa_fade_port[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
unsigned alsa_samples_recorded[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
#endif // ALSA
|
||||
};
|
||||
|
||||
|
||||
#endif // ALSADRIVER_H
|
34
cae/cae.cpp
34
cae/cae.cpp
@@ -252,7 +252,7 @@ MainObject::MainObject(QObject *parent)
|
||||
unsigned next_card=0;
|
||||
|
||||
//
|
||||
// HPI Devices (One driver instance handles them all)
|
||||
// HPI Devices
|
||||
//
|
||||
CaeDriver *dvr=CaeDriverFactory(RDStation::Hpi,this);
|
||||
if(dvr->initialize(&next_card)) {
|
||||
@@ -266,6 +266,21 @@ MainObject::MainObject(QObject *parent)
|
||||
delete dvr;
|
||||
}
|
||||
|
||||
//
|
||||
// ALSA Devices
|
||||
//
|
||||
dvr=CaeDriverFactory(RDStation::Alsa,this);
|
||||
if(dvr->initialize(&next_card)) {
|
||||
connect(dvr,SIGNAL(playStateChanged(int,int,int)),
|
||||
this,SLOT(statePlayUpdate(int,int,int)));
|
||||
connect(dvr,SIGNAL(recordStateChanged(int,int,int)),
|
||||
this,SLOT(stateRecordUpdate(int,int,int)));
|
||||
d_drivers.push_back(dvr);
|
||||
}
|
||||
else {
|
||||
delete dvr;
|
||||
}
|
||||
|
||||
//
|
||||
// Probe Capabilities
|
||||
//
|
||||
@@ -300,6 +315,7 @@ MainObject::MainObject(QObject *parent)
|
||||
if(jack_client!=NULL) {
|
||||
pthread_getschedparam(jack_client_thread_id(jack_client),&sched_policy,
|
||||
&sched_params);
|
||||
/*
|
||||
#ifdef ALSA
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
if(cae_driver[i]==RDStation::Alsa) {
|
||||
@@ -320,6 +336,7 @@ MainObject::MainObject(QObject *parent)
|
||||
}
|
||||
}
|
||||
#endif // ALSA
|
||||
*/
|
||||
jack_running=true;
|
||||
}
|
||||
#endif // JACK
|
||||
@@ -328,6 +345,7 @@ MainObject::MainObject(QObject *parent)
|
||||
sched_params.sched_priority=rda->config()->realtimePriority();
|
||||
}
|
||||
sched_policy=SCHED_FIFO;
|
||||
/*
|
||||
#ifdef ALSA
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
if(cae_driver[i]==RDStation::Alsa) {
|
||||
@@ -348,6 +366,7 @@ MainObject::MainObject(QObject *parent)
|
||||
}
|
||||
}
|
||||
#endif // ALSA
|
||||
*/
|
||||
if(sched_params.sched_priority>sched_get_priority_min(sched_policy)) {
|
||||
sched_params.sched_priority--;
|
||||
}
|
||||
@@ -1065,6 +1084,7 @@ void MainObject::connectionDroppedData(int id)
|
||||
|
||||
void MainObject::statePlayUpdate(int card,int stream,int state)
|
||||
{
|
||||
printf("statePlayUpdate(%d,%d,%d)\n",card,stream,state);
|
||||
int handle=GetHandle(card,stream);
|
||||
|
||||
if(handle<0) {
|
||||
@@ -1143,8 +1163,12 @@ void MainObject::updateMeters()
|
||||
exit(0);
|
||||
}
|
||||
|
||||
AlsaClock();
|
||||
JackClock();
|
||||
//
|
||||
// Service Disk Buffers
|
||||
//
|
||||
for(int i=0;i<d_drivers.size();i++) {
|
||||
d_drivers.at(i)->processBuffers();
|
||||
}
|
||||
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
CaeDriver *dvr=GetDriver(i);
|
||||
@@ -1539,7 +1563,7 @@ bool MainObject::CheckMp4Decode()
|
||||
bool MainObject::LoadTwoLame()
|
||||
{
|
||||
#ifdef HAVE_TWOLAME
|
||||
if((twolame_handle=dlopen("libtwolame.so.0",RTLD_NOW))==NULL) {
|
||||
if((twolame_handle=dlopen("libtwolame.so.0",RTLD_LAZY))==NULL) {
|
||||
rda->syslog(LOG_INFO,
|
||||
"TwoLAME encoder library not found, MPEG L2 encoding not supported");
|
||||
return false;
|
||||
@@ -1630,7 +1654,7 @@ void MainObject::FreeTwoLameEncoder(int card,int stream)
|
||||
bool MainObject::LoadMad()
|
||||
{
|
||||
#ifdef HAVE_MAD
|
||||
if((mad_handle=dlopen("libmad.so.0",RTLD_NOW))==NULL) {
|
||||
if((mad_handle=dlopen("libmad.so.0",RTLD_LAZY))==NULL) {
|
||||
rda->syslog(LOG_INFO,
|
||||
"MAD decoder library not found, MPEG L2 decoding not supported");
|
||||
return false;
|
||||
|
@@ -90,7 +90,7 @@ void src_float_to_int_array (const float *in, int *out, int len);
|
||||
//
|
||||
// Global CAE Definitions
|
||||
//
|
||||
#define RINGBUFFER_SIZE 262144
|
||||
//#define RINGBUFFER_SIZE 262144
|
||||
#define CAED_USAGE "[-d]\n\nSupplying the '-d' flag will set 'debug' mode, causing caed(8) to stay\nin the foreground and print debugging info on standard output.\n"
|
||||
|
||||
//
|
||||
@@ -317,6 +317,7 @@ class MainObject : public QObject
|
||||
//
|
||||
// ALSA Driver
|
||||
//
|
||||
/*
|
||||
private slots:
|
||||
void alsaStopTimerData(int cardstream);
|
||||
void alsaFadeTimerData(int cardstream);
|
||||
@@ -381,6 +382,7 @@ class MainObject : public QObject
|
||||
int alsa_fade_port[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
unsigned alsa_samples_recorded[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
#endif // ALSA
|
||||
*/
|
||||
|
||||
bool CheckLame();
|
||||
bool CheckMp4Decode();
|
||||
|
532
cae/cae_hpi.cpp
532
cae/cae_hpi.cpp
@@ -1,532 +0,0 @@
|
||||
// cae_hpi.cpp
|
||||
//
|
||||
// The HPI Driver for the Core Audio Engine component of Rivendell
|
||||
//
|
||||
// (C) Copyright 2002-2021 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.
|
||||
//
|
||||
|
||||
#include <syslog.h>
|
||||
|
||||
#include <rdapplication.h>
|
||||
#include <rdconf.h>
|
||||
#include <rddebug.h>
|
||||
|
||||
#include "cae.h"
|
||||
/*
|
||||
void MainObject::hpiInit(RDStation *station)
|
||||
{
|
||||
#ifdef HPI
|
||||
for(int i=0;i<RD_MAX_CARDS;i++) {
|
||||
for(int j=0;j<RD_MAX_STREAMS;j++) {
|
||||
record[i][j]=NULL;
|
||||
play[i][j]=NULL;
|
||||
}
|
||||
}
|
||||
sound_card=new RDHPISoundCard(rd_config,this);
|
||||
sound_card->setFadeProfile(RD_FADE_TYPE);
|
||||
for(int i=0;i<sound_card->getCardQuantity();i++) {
|
||||
cae_driver[i]=RDStation::Hpi;
|
||||
station->setCardDriver(i,RDStation::Hpi);
|
||||
station->setCardName(i,sound_card->getCardDescription(i));
|
||||
station->setCardInputs(i,sound_card->getCardInputPorts(i));
|
||||
station->setCardOutputs(i,sound_card->getCardOutputPorts(i));
|
||||
}
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
void MainObject::hpiFree()
|
||||
{
|
||||
#ifdef HPI
|
||||
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
QString MainObject::hpiVersion()
|
||||
{
|
||||
#ifdef HPI
|
||||
if(sound_card==NULL) {
|
||||
return QString("not active");
|
||||
}
|
||||
RDHPIInformation *info=sound_card->hpiInformation(0);
|
||||
if(info->hpiVersion()==0) {
|
||||
return QString("not active");
|
||||
}
|
||||
return QString().sprintf("%d.%02d.%02d",info->hpiMajorVersion(),
|
||||
info->hpiMinorVersion(),info->hpiPointVersion());
|
||||
#else
|
||||
return QString("not enabled");
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiLoadPlayback(int card,QString wavename,int *stream)
|
||||
{
|
||||
#ifdef HPI
|
||||
RDHPIPlayStream *playstream=new RDHPIPlayStream(sound_card);
|
||||
playstream->setCard(card);
|
||||
if(playstream->openWave(wavename)!=RDHPIPlayStream::Ok) {
|
||||
RDApplication::syslog(rd_config,LOG_DEBUG,
|
||||
"hpiLoadPlayback(%s) openWave() failed to open file",
|
||||
(const char *)wavename.toUtf8());
|
||||
delete playstream;
|
||||
return false;
|
||||
}
|
||||
*stream=playstream->getStream();
|
||||
play[card][*stream]=playstream;
|
||||
connect(play[card][*stream],SIGNAL(stateChanged(int,int,int)),
|
||||
this,SLOT(statePlayUpdate(int,int,int)));
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiUnloadPlayback(int card,int stream)
|
||||
{
|
||||
#ifdef HPI
|
||||
if(play[card][stream]==NULL) {
|
||||
return false;
|
||||
}
|
||||
if(play[card][stream]->getState()==RDHPIPlayStream::Playing) {
|
||||
play[card][stream]->pause();
|
||||
}
|
||||
play[card][stream]->disconnect();
|
||||
play[card][stream]->closeWave();
|
||||
delete play[card][stream];
|
||||
play[card][stream]=NULL;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiPlaybackPosition(int card,int stream,unsigned pos)
|
||||
{
|
||||
#ifdef HPI
|
||||
if(play[card][stream]==NULL) {
|
||||
return false;
|
||||
}
|
||||
return play[card][stream]->
|
||||
setPosition((unsigned)((double)play[card][stream]->getSamplesPerSec()*
|
||||
(double)pos/1000.0));
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiPlay(int card,int stream,int length,int speed,bool pitch,
|
||||
bool rates)
|
||||
{
|
||||
#ifdef HPI
|
||||
if(play[card][stream]==NULL) {
|
||||
return false;
|
||||
}
|
||||
if(!play[card][stream]->setSpeed(speed,pitch,rates)) {
|
||||
return false;
|
||||
}
|
||||
play[card][stream]->setPlayLength(length);
|
||||
return play[card][stream]->play();
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiStopPlayback(int card,int stream)
|
||||
{
|
||||
#ifdef HPI
|
||||
if(play[card][stream]==NULL) {
|
||||
return false;
|
||||
}
|
||||
play[card][stream]->pause();
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiTimescaleSupported(int card)
|
||||
{
|
||||
#ifdef HPI
|
||||
return sound_card->haveTimescaling(card);
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiLoadRecord(int card,int stream,int coding,int chans,
|
||||
int samprate,int bitrate,QString wavename)
|
||||
{
|
||||
#ifdef HPI
|
||||
record[card][stream]=new RDHPIRecordStream(sound_card);
|
||||
connect(record[card][stream],SIGNAL(stateChanged(int,int,int)),
|
||||
this,SLOT(stateRecordUpdate(int,int,int)));
|
||||
record[card][stream]->setCard(card);
|
||||
record[card][stream]->setStream(stream);
|
||||
record[card][stream]->nameWave(wavename);
|
||||
record[card][stream]->setChannels(chans);
|
||||
record[card][stream]->setSamplesPerSec(samprate);
|
||||
if(coding==0) { // PCM16
|
||||
record[card][stream]->setFormatTag(WAVE_FORMAT_PCM);
|
||||
record[card][stream]->setBitsPerSample(16);
|
||||
}
|
||||
if((coding==1)||(coding==2)) { // MPEG-1
|
||||
record[card][stream]->setFormatTag(WAVE_FORMAT_MPEG);
|
||||
record[card][stream]->setHeadLayer(coding);
|
||||
record[card][stream]->setHeadBitRate(bitrate);
|
||||
record[card][stream]->setMextChunk(true);
|
||||
switch(chans) {
|
||||
case 1:
|
||||
record[card][stream]->setHeadMode(ACM_MPEG_SINGLECHANNEL);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
record[card][stream]->setHeadMode(ACM_MPEG_STEREO);
|
||||
break;
|
||||
|
||||
default:
|
||||
delete record[card][stream];
|
||||
record[card][stream]=NULL;
|
||||
return false;
|
||||
}
|
||||
record[card][stream]->setHeadFlags(ACM_MPEG_ID_MPEG1);
|
||||
}
|
||||
if(coding==4) { // PCM24
|
||||
record[card][stream]->setFormatTag(WAVE_FORMAT_PCM);
|
||||
record[card][stream]->setBitsPerSample(24);
|
||||
}
|
||||
if(coding>4) {
|
||||
delete record[card][stream];
|
||||
record[card][stream]=NULL;
|
||||
return false;
|
||||
}
|
||||
record[card][stream]->setBextChunk(true);
|
||||
record[card][stream]->setCartChunk(true);
|
||||
record[card][stream]->setLevlChunk(true);
|
||||
if(record[card][stream]->createWave()!=RDHPIRecordStream::Ok) {
|
||||
delete record[card][stream];
|
||||
record[card][stream]=NULL;
|
||||
return false;
|
||||
}
|
||||
RDCheckExitCode(rd_config,"hpiLoadRecord() chown",
|
||||
chown(wavename.toUtf8(),rd_config->uid(),rd_config->gid()));
|
||||
if(!record[card][stream]->recordReady()) {
|
||||
delete record[card][stream];
|
||||
record[card][stream]=NULL;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiUnloadRecord(int card,int stream,unsigned *len)
|
||||
{
|
||||
#ifdef HPI
|
||||
if(record[card][stream]==NULL) {
|
||||
return false;
|
||||
}
|
||||
if(record[card][stream]->getState()==RDHPIRecordStream::Recording) {
|
||||
record[card][stream]->pause();
|
||||
}
|
||||
record[card][stream]->disconnect();
|
||||
*len=record[card][stream]->samplesRecorded();
|
||||
record[card][stream]->closeWave();
|
||||
delete record[card][stream];
|
||||
record[card][stream]=NULL;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiRecord(int card,int stream,int length,int thres)
|
||||
{
|
||||
#ifdef HPI
|
||||
if(record[card][stream]==NULL) {
|
||||
return false;
|
||||
}
|
||||
if(thres!=0) {
|
||||
if(record[card][stream]->haveInputVOX()) {
|
||||
record[card][stream]->setInputVOX(thres);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
record[card][stream]->setRecordLength(length);
|
||||
record[card][stream]->record();
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiStopRecord(int card,int stream)
|
||||
{
|
||||
#ifdef HPI
|
||||
if(record[card][stream]==NULL) {
|
||||
return false;
|
||||
}
|
||||
record[card][stream]->pause();
|
||||
record[card][stream]->setInputVOX(-10000);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetClockSource(int card,int src)
|
||||
{
|
||||
#ifdef HPI
|
||||
return sound_card->setClockSource(card,(RDHPISoundCard::ClockSource)src);
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetInputVolume(int card,int stream,int level)
|
||||
{
|
||||
#ifdef HPI
|
||||
sound_card->setInputVolume(card,stream,level);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetOutputVolume(int card,int stream,int port,int level)
|
||||
{
|
||||
#ifdef HPI
|
||||
sound_card->setOutputVolume(card,stream,port,level);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiFadeOutputVolume(int card,int stream,int port,int level,
|
||||
int length)
|
||||
{
|
||||
#ifdef HPI
|
||||
sound_card->fadeOutputVolume(card,stream,port,level,length);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetInputLevel(int card,int port,int level)
|
||||
{
|
||||
#ifdef HPI
|
||||
sound_card->setInputLevel(card,port,level);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetOutputLevel(int card,int port,int level)
|
||||
{
|
||||
#ifdef HPI
|
||||
sound_card->setOutputLevel(card,port,level);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetInputMode(int card,int stream,int mode)
|
||||
{
|
||||
#ifdef HPI
|
||||
switch(mode) {
|
||||
case 0:
|
||||
sound_card->setInputMode(card,stream,RDHPISoundCard::Normal);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
sound_card->setInputMode(card,stream,RDHPISoundCard::Swap);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
sound_card->setInputMode(card,stream,RDHPISoundCard::LeftOnly);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
sound_card->setInputMode(card,stream,RDHPISoundCard::RightOnly);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetOutputMode(int card,int stream,int mode)
|
||||
{
|
||||
#ifdef HPI
|
||||
switch(mode) {
|
||||
case 0:
|
||||
sound_card->setOutputMode(card,stream,RDHPISoundCard::Normal);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
sound_card->setOutputMode(card,stream,RDHPISoundCard::Swap);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
sound_card->setOutputMode(card,stream,RDHPISoundCard::LeftOnly);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
sound_card->setOutputMode(card,stream,RDHPISoundCard::RightOnly);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetInputVoxLevel(int card,int stream,int level)
|
||||
{
|
||||
#ifdef HPI
|
||||
sound_card->setInputStreamVOX(card,stream,level);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetInputType(int card,int port,int type)
|
||||
{
|
||||
#ifdef HPI
|
||||
switch(type) {
|
||||
case 0:
|
||||
sound_card->setInputPortMux(card,port,RDHPISoundCard::LineIn);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
sound_card->setInputPortMux(card,port,RDHPISoundCard::AesEbuIn);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiGetInputStatus(int card,int port)
|
||||
{
|
||||
#ifdef HPI
|
||||
if(sound_card->getInputPortError(card,port)!=0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiGetInputMeters(int card,int port,short levels[2])
|
||||
{
|
||||
#ifdef HPI
|
||||
return sound_card->inputStreamMeter(card,port,levels);
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiGetOutputMeters(int card,int port,short levels[2])
|
||||
{
|
||||
#ifdef HPI
|
||||
return sound_card->outputPortMeter(card,port,levels);
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiGetStreamOutputMeters(int card,int stream,short levels[2])
|
||||
{
|
||||
#ifdef HPI
|
||||
return sound_card->outputStreamMeter(card,stream,levels);
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
|
||||
|
||||
void MainObject::hpiGetOutputPosition(int card,unsigned *pos)
|
||||
{
|
||||
#ifdef HPI
|
||||
for(int i=0;i<RD_MAX_STREAMS;i++) {
|
||||
if(play[card][i]==NULL) {
|
||||
pos[i]=0;
|
||||
}
|
||||
else {
|
||||
pos[i]=1000*(unsigned long long)play[card][i]->currentPosition()/
|
||||
play[card][i]->getSamplesPerSec();
|
||||
}
|
||||
}
|
||||
#endif // HPI
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
bool MainObject::hpiSetPassthroughLevel(int card,int in_port,int out_port,
|
||||
int level)
|
||||
{
|
||||
#ifdef HPI
|
||||
return sound_card->setPassthroughVolume(card,in_port,out_port,level);
|
||||
#else
|
||||
return false;
|
||||
#endif // HPI
|
||||
}
|
||||
*/
|
@@ -18,12 +18,17 @@
|
||||
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
//
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "caedriver.h"
|
||||
|
||||
CaeDriver::CaeDriver(RDStation::AudioDriver type,QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
d_driver_type=type;
|
||||
d_system_sample_rate=rda->system()->sampleRate();
|
||||
twolame_handle=NULL;
|
||||
mad_handle=NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +44,11 @@ bool CaeDriver::hasCard(int cardnum) const
|
||||
}
|
||||
|
||||
|
||||
void CaeDriver::processBuffers()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CaeDriver::statePlayUpdate(int card,int stream,int state)
|
||||
{
|
||||
emit playStateChanged(card,stream,state);
|
||||
@@ -63,3 +73,176 @@ void CaeDriver::addCard(unsigned cardnum)
|
||||
d_cards.push_back(cardnum);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned CaeDriver::systemSampleRate() const
|
||||
{
|
||||
return d_system_sample_rate;
|
||||
}
|
||||
|
||||
|
||||
bool CaeDriver::LoadTwoLame()
|
||||
{
|
||||
#ifdef HAVE_TWOLAME
|
||||
if((twolame_handle=dlopen("libtwolame.so.0",RTLD_NOW))==NULL) {
|
||||
rda->syslog(LOG_INFO,
|
||||
"TwoLAME encoder library not found, MPEG L2 encoding not supported");
|
||||
return false;
|
||||
}
|
||||
*(void **)(&twolame_init)=dlsym(twolame_handle,"twolame_init");
|
||||
*(void **)(&twolame_set_mode)=dlsym(twolame_handle,"twolame_set_mode");
|
||||
*(void **)(&twolame_set_num_channels)=
|
||||
dlsym(twolame_handle,"twolame_set_num_channels");
|
||||
*(void **)(&twolame_set_in_samplerate)=
|
||||
dlsym(twolame_handle,"twolame_set_in_samplerate");
|
||||
*(void **)(&twolame_set_out_samplerate)=
|
||||
dlsym(twolame_handle,"twolame_set_out_samplerate");
|
||||
*(void **)(&twolame_set_bitrate)=
|
||||
dlsym(twolame_handle,"twolame_set_bitrate");
|
||||
*(void **)(&twolame_init_params)=
|
||||
dlsym(twolame_handle,"twolame_init_params");
|
||||
*(void **)(&twolame_close)=dlsym(twolame_handle,"twolame_close");
|
||||
*(void **)(&twolame_encode_buffer_interleaved)=
|
||||
dlsym(twolame_handle,"twolame_encode_buffer_interleaved");
|
||||
*(void **)(&twolame_encode_buffer_float32_interleaved)=
|
||||
dlsym(twolame_handle,"twolame_encode_buffer_float32_interleaved");
|
||||
*(void **)(&twolame_encode_flush)=
|
||||
dlsym(twolame_handle,"twolame_encode_flush");
|
||||
*(void **)(&twolame_set_energy_levels)=
|
||||
dlsym(twolame_handle,"twolame_set_energy_levels");
|
||||
rda->syslog(LOG_INFO,
|
||||
"Found TwoLAME encoder library, MPEG L2 encoding supported");
|
||||
return true;
|
||||
#else
|
||||
rda->syslog(LOG_INFO,"MPEG L2 encoding not enabled");
|
||||
|
||||
return false;
|
||||
#endif // HAVE_TWOLAME
|
||||
}
|
||||
|
||||
|
||||
bool CaeDriver::InitTwoLameEncoder(int card,int stream,int chans,int samprate,
|
||||
int bitrate)
|
||||
{
|
||||
if(twolame_handle==NULL) {
|
||||
rda->syslog(LOG_WARNING,"MPEG Layer 2 encode not available");
|
||||
return false;
|
||||
}
|
||||
#ifdef HAVE_TWOLAME
|
||||
TWOLAME_MPEG_mode mpeg_mode=TWOLAME_STEREO;
|
||||
|
||||
switch(chans) {
|
||||
case 1:
|
||||
mpeg_mode=TWOLAME_MONO;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
mpeg_mode=TWOLAME_STEREO;
|
||||
break;
|
||||
}
|
||||
if((twolame_lameopts[card][stream]=twolame_init())==NULL) {
|
||||
rda->syslog(LOG_WARNING,
|
||||
"unable to initialize twolame instance, card=%d, stream=%d",
|
||||
card,stream);
|
||||
return false;
|
||||
}
|
||||
twolame_set_mode(twolame_lameopts[card][stream],mpeg_mode);
|
||||
twolame_set_num_channels(twolame_lameopts[card][stream],chans);
|
||||
twolame_set_in_samplerate(twolame_lameopts[card][stream],samprate);
|
||||
twolame_set_out_samplerate(twolame_lameopts[card][stream],samprate);
|
||||
twolame_set_bitrate(twolame_lameopts[card][stream],bitrate/1000);
|
||||
twolame_set_energy_levels(twolame_lameopts[card][stream],1);
|
||||
if(twolame_init_params(twolame_lameopts[card][stream])!=0) {
|
||||
rda->syslog(LOG_WARNING,
|
||||
"invalid twolame parameters, card=%d, stream=%d, chans=%d, samprate=%d bitrate=%d",
|
||||
card,stream,chans,samprate,bitrate);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // HAVE_TWOLAME
|
||||
}
|
||||
|
||||
|
||||
void CaeDriver::FreeTwoLameEncoder(int card,int stream)
|
||||
{
|
||||
#ifdef HAVE_TWOLAME
|
||||
if(twolame_lameopts[card][stream]!=NULL) {
|
||||
twolame_close(&twolame_lameopts[card][stream]);
|
||||
twolame_lameopts[card][stream]=NULL;
|
||||
}
|
||||
#endif // HAVE_TWOLAME
|
||||
}
|
||||
|
||||
|
||||
bool CaeDriver::LoadMad()
|
||||
{
|
||||
#ifdef HAVE_MAD
|
||||
if((mad_handle=dlopen("libmad.so.0",RTLD_NOW))==NULL) {
|
||||
rda->syslog(LOG_INFO,
|
||||
"MAD decoder library not found, MPEG L2 decoding not supported");
|
||||
return false;
|
||||
}
|
||||
*(void **)(&mad_stream_init)=
|
||||
dlsym(mad_handle,"mad_stream_init");
|
||||
*(void **)(&mad_frame_init)=
|
||||
dlsym(mad_handle,"mad_frame_init");
|
||||
*(void **)(&mad_synth_init)=
|
||||
dlsym(mad_handle,"mad_synth_init");
|
||||
*(void **)(&mad_stream_buffer)=
|
||||
dlsym(mad_handle,"mad_stream_buffer");
|
||||
*(void **)(&mad_frame_decode)=
|
||||
dlsym(mad_handle,"mad_frame_decode");
|
||||
*(void **)(&mad_synth_frame)=
|
||||
dlsym(mad_handle,"mad_synth_frame");
|
||||
*(void **)(&mad_frame_finish)=
|
||||
dlsym(mad_handle,"mad_frame_finish");
|
||||
*(void **)(&mad_stream_finish)=
|
||||
dlsym(mad_handle,"mad_stream_finish");
|
||||
rda->syslog(LOG_INFO,
|
||||
"Found MAD decoder library, MPEG L2 decoding supported");
|
||||
return true;
|
||||
#else
|
||||
rda->syslog(LOG_INFO,"MPEG L2 decoding not enabled");
|
||||
return false;
|
||||
#endif // HAVE_MAD
|
||||
}
|
||||
|
||||
|
||||
bool CaeDriver::InitMadDecoder(int card,int stream,RDWaveFile *wave)
|
||||
{
|
||||
if(mad_handle==NULL) {
|
||||
rda->syslog(LOG_WARNING,"MPEG Layer 2 decode not available");
|
||||
return false;
|
||||
}
|
||||
#ifdef HAVE_MAD
|
||||
if(mad_active[card][stream]) {
|
||||
FreeMadDecoder(card,stream);
|
||||
}
|
||||
mad_stream_init(&mad_stream[card][stream]);
|
||||
mad_frame_init(&mad_frame[card][stream]);
|
||||
mad_synth_init(&mad_synth[card][stream]);
|
||||
mad_frame_size[card][stream]=
|
||||
144*wave->getHeadBitRate()/wave->getSamplesPerSec();
|
||||
mad_left_over[card][stream]=0;
|
||||
mad_active[card][stream]=true;
|
||||
return true;
|
||||
#endif // HAVE_MAD
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CaeDriver::FreeMadDecoder(int card,int stream)
|
||||
{
|
||||
#ifdef HAVE_MAD
|
||||
if(mad_active[card][stream]) {
|
||||
mad_synth_finish(&mad_synth[card][stream]);
|
||||
mad_frame_finish(&mad_frame[card][stream]);
|
||||
mad_stream_finish(&mad_stream[card][stream]);
|
||||
mad_frame_size[card][stream]=0;
|
||||
mad_left_over[card][stream]=0;
|
||||
mad_active[card][stream]=false;
|
||||
}
|
||||
#endif // HAVE_MAD
|
||||
}
|
||||
|
@@ -21,10 +21,23 @@
|
||||
#ifndef CAEDRIVER_H
|
||||
#define CAEDRIVER_H
|
||||
|
||||
#ifdef HAVE_TWOLAME
|
||||
#include <twolame.h>
|
||||
#endif // HAVE_TWOLAME
|
||||
#ifdef HAVE_MAD
|
||||
#include <mad.h>
|
||||
#endif // HAVE_MAD
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSignalMapper>
|
||||
|
||||
#include <rdapplication.h>
|
||||
#include <rdwavefile.h>
|
||||
|
||||
#define RINGBUFFER_SIZE 262144
|
||||
|
||||
extern void SigHandler(int signum);
|
||||
|
||||
class CaeDriver : public QObject
|
||||
{
|
||||
@@ -73,17 +86,73 @@ class CaeDriver : public QObject
|
||||
void playStateChanged(int card,int stream,int state);
|
||||
void recordStateChanged(int card,int stream,int state);
|
||||
|
||||
public slots:
|
||||
virtual void processBuffers();
|
||||
|
||||
protected slots:
|
||||
void statePlayUpdate(int card,int stream,int state);
|
||||
void stateRecordUpdate(int card,int stream,int state);
|
||||
|
||||
protected:
|
||||
void addCard(unsigned cardnum);
|
||||
unsigned systemSampleRate() const;
|
||||
RDConfig *config() const;
|
||||
//
|
||||
// TwoLAME Encoder
|
||||
//
|
||||
bool LoadTwoLame();
|
||||
bool InitTwoLameEncoder(int card,int stream,int chans,int samprate,
|
||||
int bitrate);
|
||||
void FreeTwoLameEncoder(int card,int stream);
|
||||
void *twolame_handle;
|
||||
#ifdef HAVE_TWOLAME
|
||||
twolame_options *(*twolame_init)(void);
|
||||
void (*twolame_set_mode)(twolame_options *,TWOLAME_MPEG_mode);
|
||||
void (*twolame_set_num_channels)(twolame_options *,int);
|
||||
void (*twolame_set_in_samplerate)(twolame_options *,int);
|
||||
void (*twolame_set_out_samplerate)(twolame_options *,int);
|
||||
void (*twolame_set_bitrate)(twolame_options *,int);
|
||||
int (*twolame_init_params)(twolame_options *);
|
||||
void (*twolame_close)(twolame_options **);
|
||||
int (*twolame_encode_buffer_interleaved)(twolame_options *,const short int[],
|
||||
int,unsigned char *,int);
|
||||
int (*twolame_encode_buffer_float32_interleaved)
|
||||
(twolame_options *,const float[],int,unsigned char *,int);
|
||||
int (*twolame_encode_flush)(twolame_options *,unsigned char *,int);
|
||||
int (*twolame_set_energy_levels)(twolame_options *,int);
|
||||
twolame_options *twolame_lameopts[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
#endif // HAVE_TWOLAME
|
||||
|
||||
//
|
||||
// MAD Decoder
|
||||
//
|
||||
bool LoadMad();
|
||||
bool InitMadDecoder(int card,int stream,RDWaveFile *wave);
|
||||
void FreeMadDecoder(int card,int stream);
|
||||
void *mad_handle;
|
||||
#ifdef HAVE_MAD
|
||||
void (*mad_stream_init)(struct mad_stream *);
|
||||
void (*mad_frame_init)(struct mad_frame *);
|
||||
void (*mad_synth_init)(struct mad_synth *);
|
||||
void (*mad_stream_buffer)(struct mad_stream *,unsigned char const *,
|
||||
unsigned long);
|
||||
int (*mad_frame_decode)(struct mad_frame *, struct mad_stream *);
|
||||
void (*mad_synth_frame)(struct mad_synth *, struct mad_frame const *);
|
||||
void (*mad_frame_finish)(struct mad_frame *);
|
||||
void (*mad_stream_finish)(struct mad_stream *);
|
||||
struct mad_stream mad_stream[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
struct mad_frame mad_frame[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
struct mad_synth mad_synth[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
bool mad_active[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
int mad_frame_size[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
int mad_left_over[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
unsigned char *mad_mpeg[RD_MAX_CARDS][RD_MAX_STREAMS];
|
||||
#endif // HAVE_MAD
|
||||
|
||||
private:
|
||||
RDStation::AudioDriver d_driver_type;
|
||||
QList<unsigned> d_cards;
|
||||
unsigned d_system_sample_rate;
|
||||
};
|
||||
|
||||
|
||||
|
@@ -18,26 +18,29 @@
|
||||
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
//
|
||||
|
||||
#include "alsadriver.h"
|
||||
#include "caedriverfactory.h"
|
||||
#include "hpidriver.h"
|
||||
|
||||
CaeDriver *CaeDriverFactory(RDStation::AudioDriver dvr,QObject *parent)
|
||||
{
|
||||
CaeDriver *ret=NULL;
|
||||
printf("HERE1\n");
|
||||
|
||||
switch(dvr) {
|
||||
case RDStation::Hpi:
|
||||
printf("HERE2\n");
|
||||
ret=new HpiDriver(parent);
|
||||
break;
|
||||
|
||||
case RDStation::Jack:
|
||||
break;
|
||||
|
||||
case RDStation::Alsa:
|
||||
ret=new AlsaDriver(parent);
|
||||
break;
|
||||
|
||||
case RDStation::None:
|
||||
printf("HERE3\n");
|
||||
break;
|
||||
}
|
||||
printf("HERE4: %p\n",ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
Reference in New Issue
Block a user