kopia lustrzana https://github.com/jamescoxon/dl-fldigi
1141 wiersze
32 KiB
C++
1141 wiersze
32 KiB
C++
// ----------------------------------------------------------------------------
|
|
//
|
|
// sound.cxx
|
|
//
|
|
// Copyright (C) 2006-2007
|
|
// Dave Freese, W1HKJ
|
|
//
|
|
// Copyright (C) 2007
|
|
// Stelios Bounanos, M0GLD
|
|
//
|
|
// This file is part of fldigi.
|
|
//
|
|
// fldigi is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// fldigi 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 fldigi; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include <config.h>
|
|
|
|
#include "sound.h"
|
|
#include "configuration.h"
|
|
#include <FL/Fl.H>
|
|
#include "File_Selector.h"
|
|
|
|
|
|
cSound::cSound()
|
|
: sample_frequency(0), txppm(progdefaults.TX_corr), rxppm(progdefaults.RX_corr),
|
|
tx_src_state(0), tx_src_data(0), rx_src_state(0), rx_src_data(0),
|
|
snd_buffer(0), src_buffer(0),
|
|
#if USE_SNDFILE
|
|
ofCapture(0), ifPlayback(0), ofGenerate(0),
|
|
#endif
|
|
capture(false), playback(false), generate(false)
|
|
{ }
|
|
|
|
cSound::~cSound()
|
|
{
|
|
if (snd_buffer) delete [] snd_buffer;
|
|
if (src_buffer) delete [] src_buffer;
|
|
if (tx_src_data) delete tx_src_data;
|
|
if (rx_src_data) delete rx_src_data;
|
|
if (rx_src_state) src_delete (rx_src_state);
|
|
if (tx_src_state) src_delete (tx_src_state);
|
|
#if USE_SNDFILE
|
|
delete ofGenerate;
|
|
delete ofCapture;
|
|
delete ifPlayback;
|
|
#endif
|
|
}
|
|
|
|
#if USE_SNDFILE
|
|
int cSound::Capture(bool on)
|
|
{
|
|
if (on) {
|
|
string deffilename = "./capture.wav";
|
|
const char *formats;
|
|
if (format_supported(SF_FORMAT_FLAC | SF_FORMAT_PCM_16))
|
|
formats = "*.{wav,flac}";
|
|
else
|
|
formats = "*.wav";
|
|
|
|
char *fn = File_Select("Capture audio file", formats, deffilename.c_str(), 0);
|
|
if (fn) {
|
|
int format;
|
|
char *suffix = strrchr(fn, '.');
|
|
if (suffix && !strcasecmp(suffix, ".flac"))
|
|
format = SF_FORMAT_FLAC;
|
|
else
|
|
format = SF_FORMAT_WAV;
|
|
ofCapture = new SndfileHandle(fn, SFM_WRITE,
|
|
format | SF_FORMAT_PCM_16,
|
|
1, sample_frequency);
|
|
if (!*ofCapture) {
|
|
cerr << "Could not write " << fn << endl;
|
|
delete ofCapture;
|
|
ofCapture = 0;
|
|
return 0;
|
|
}
|
|
ofCapture->command(SFC_SET_UPDATE_HEADER_AUTO, 0, SF_TRUE);
|
|
tag_file(ofCapture, "Captured audio");
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
else {
|
|
delete ofCapture;
|
|
ofCapture = 0;
|
|
}
|
|
capture = on;
|
|
return 1;
|
|
}
|
|
|
|
int cSound::Playback(bool on)
|
|
{
|
|
if (on) {
|
|
string deffilename = "./playback.wav";
|
|
const char *formats;
|
|
if (format_supported(SF_FORMAT_FLAC | SF_FORMAT_PCM_16))
|
|
formats = "*.{wav,flac}";
|
|
else
|
|
formats = "*.wav";
|
|
|
|
char *fn = File_Select("Playback audio file", formats, deffilename.c_str(), 0);
|
|
if (fn) {
|
|
ifPlayback = new SndfileHandle(fn);
|
|
if (!*ifPlayback) {
|
|
cerr << "Could not read " << fn << endl;
|
|
delete ifPlayback;
|
|
ifPlayback = 0;
|
|
return 0;
|
|
}
|
|
playback = true;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
else {
|
|
delete ifPlayback;
|
|
ifPlayback = 0;
|
|
playback = false;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int cSound::Generate(bool on)
|
|
{
|
|
if (on) {
|
|
string deffilename = "./generate.wav";
|
|
const char *formats;
|
|
if (format_supported(SF_FORMAT_FLAC | SF_FORMAT_PCM_16))
|
|
formats = "*.{wav,flac}";
|
|
else
|
|
formats = "*.wav";
|
|
|
|
char *fn = File_Select("Generate audio file", formats, deffilename.c_str(), 0);
|
|
if (fn) {
|
|
int format;
|
|
char *suffix = strrchr(fn, '.');
|
|
if (suffix && !strcasecmp(suffix, ".flac"))
|
|
format = SF_FORMAT_FLAC;
|
|
else
|
|
format = SF_FORMAT_WAV;
|
|
ofGenerate = new SndfileHandle(fn, SFM_WRITE,
|
|
format | SF_FORMAT_PCM_16,
|
|
1, sample_frequency);
|
|
if (!*ofGenerate) {
|
|
cerr << "Could not write " << fn << endl;
|
|
delete ofGenerate;
|
|
ofGenerate = 0;
|
|
return 0;
|
|
}
|
|
ofGenerate->command(SFC_SET_UPDATE_HEADER_AUTO, 0, SF_TRUE);
|
|
tag_file(ofGenerate, "Generated audio");
|
|
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
else {
|
|
delete ofGenerate;
|
|
ofGenerate = 0;
|
|
}
|
|
generate = on;
|
|
return 1;
|
|
}
|
|
#endif // USE_SNDFILE
|
|
|
|
void cSound::writeGenerate(double *buff, int count)
|
|
{
|
|
#if USE_SNDFILE
|
|
ofGenerate->writef(buff, count);
|
|
#endif
|
|
}
|
|
|
|
void cSound::writeCapture(double *buff, int count)
|
|
{
|
|
#if USE_SNDFILE
|
|
ofCapture->writef(buff, count);
|
|
#endif
|
|
}
|
|
|
|
int cSound::readPlayback(double *buff, int count)
|
|
{
|
|
#if USE_SNDFILE
|
|
sf_count_t r = ifPlayback->readf(buff, count);
|
|
|
|
while (r < count) {
|
|
ifPlayback->seek(0, SEEK_SET);
|
|
r += ifPlayback->readf(buff + r, count - r);
|
|
if (r == 0)
|
|
break;
|
|
}
|
|
|
|
return r;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
#if USE_SNDFILE
|
|
bool cSound::format_supported(int format)
|
|
{
|
|
|
|
SF_INFO fmt_test = { 0, sample_frequency, 2, format, 0, 0 };
|
|
return sf_format_check(&fmt_test);
|
|
}
|
|
|
|
void cSound::tag_file(SndfileHandle *fh, const char *title)
|
|
{
|
|
if (fh->setString(SF_STR_TITLE, title))
|
|
return;
|
|
|
|
fh->setString(SF_STR_COPYRIGHT, progdefaults.myName.c_str());
|
|
fh->setString(SF_STR_SOFTWARE, PACKAGE_NAME "-" PACKAGE_VERSION);
|
|
fh->setString(SF_STR_ARTIST, progdefaults.myCall.c_str());
|
|
|
|
char s[64];
|
|
snprintf(s, sizeof(s), "%s freq=%s",
|
|
active_modem->get_mode_name(), inpFreq->value());
|
|
fh->setString(SF_STR_COMMENT, s);
|
|
|
|
time_t t = time(0);
|
|
struct tm zt;
|
|
(void)gmtime_r(&t, &zt);
|
|
if (strftime(s, sizeof(s), "%F %Tz", &zt) > 0)
|
|
fh->setString(SF_STR_DATE, s);
|
|
}
|
|
#endif // USE_SNDFILE
|
|
|
|
cSoundOSS::cSoundOSS(const char *dev ) {
|
|
device = dev;
|
|
cbuff = 0;
|
|
try {
|
|
Open(O_RDONLY);
|
|
getVersion();
|
|
getCapabilities();
|
|
getFormats();
|
|
Close();
|
|
}
|
|
catch (SndException e) {
|
|
std::cout << e.what()
|
|
<< " <" << device.c_str()
|
|
<< ">" << std::endl;
|
|
}
|
|
|
|
int err;
|
|
try {
|
|
snd_buffer = new float [2*SND_BUF_LEN];
|
|
src_buffer = new float [2*SND_BUF_LEN];
|
|
cbuff = new unsigned char [4 * SND_BUF_LEN];
|
|
}
|
|
catch (const std::bad_alloc& e) {
|
|
cerr << "Cannot allocate libsamplerate buffers\n";
|
|
throw;
|
|
}
|
|
for (int i = 0; i < 2*SND_BUF_LEN; i++)
|
|
snd_buffer[i] = src_buffer[i] = 0.0;
|
|
for (int i = 0; i < 4 * SND_BUF_LEN; i++)
|
|
cbuff[i] = 0;
|
|
|
|
try {
|
|
tx_src_data = new SRC_DATA;
|
|
rx_src_data = new SRC_DATA;
|
|
}
|
|
catch (const std::bad_alloc& e) {
|
|
cerr << "Cannot create libsamplerate data structures\n";
|
|
throw;
|
|
}
|
|
|
|
rx_src_state = src_new(SRC_SINC_FASTEST, 2, &err);
|
|
if (rx_src_state == 0)
|
|
throw SndException(src_strerror(err));
|
|
|
|
tx_src_state = src_new(SRC_SINC_FASTEST, 2, &err);
|
|
if (tx_src_state == 0)
|
|
throw SndException(src_strerror(err));
|
|
|
|
rx_src_data->src_ratio = 1.0/(1.0 + rxppm/1e6);
|
|
src_set_ratio ( rx_src_state, 1.0/(1.0 + rxppm/1e6));
|
|
|
|
tx_src_data->src_ratio = 1.0 + txppm/1e6;
|
|
src_set_ratio ( tx_src_state, 1.0 + txppm/1e6);
|
|
}
|
|
|
|
cSoundOSS::~cSoundOSS()
|
|
{
|
|
Close();
|
|
if (cbuff) delete [] cbuff;
|
|
}
|
|
|
|
void cSoundOSS::setfragsize()
|
|
{
|
|
int sndparam;
|
|
// Try to get ~100ms worth of samples per fragment
|
|
sndparam = (int)log2(sample_frequency * 0.1);
|
|
// double since we are using 16 bit samples
|
|
sndparam += 1;
|
|
// Unlimited amount of buffers for RX, four for TX
|
|
if (mode == O_RDONLY)
|
|
sndparam |= 0x7FFF0000;
|
|
else
|
|
sndparam |= 0x00040000;
|
|
|
|
if (ioctl(device_fd, SNDCTL_DSP_SETFRAGMENT, &sndparam) < 0)
|
|
throw SndException(errno);
|
|
}
|
|
|
|
int cSoundOSS::Open(int md, int freq)
|
|
{
|
|
mode = md;
|
|
try {
|
|
device_fd = open(device.c_str(), mode, 0);
|
|
if (device_fd == -1)
|
|
throw SndException(errno);
|
|
Format(AFMT_S16_LE); // default: 16 bit little endian
|
|
// Channels(1); // 1 channel
|
|
Channels(2); // 2 channels
|
|
Frequency(freq);
|
|
setfragsize();
|
|
}
|
|
catch (...) {
|
|
throw;
|
|
}
|
|
return device_fd;
|
|
}
|
|
|
|
void cSoundOSS::Close()
|
|
{
|
|
if (device_fd == -1)
|
|
return;
|
|
close(device_fd);
|
|
device_fd = -1;
|
|
}
|
|
|
|
void cSoundOSS::getVersion()
|
|
{
|
|
version = 0;
|
|
#ifndef __FreeBSD__
|
|
if (ioctl(device_fd, OSS_GETVERSION, &version) == -1) {
|
|
version = -1;
|
|
throw SndException("OSS Version");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void cSoundOSS::getCapabilities()
|
|
{
|
|
capability_mask = 0;
|
|
if (ioctl(device_fd, SNDCTL_DSP_GETCAPS, &capability_mask) == -1) {
|
|
capability_mask = 0;
|
|
throw SndException("OSS capabilities");
|
|
}
|
|
}
|
|
|
|
void cSoundOSS::getFormats()
|
|
{
|
|
format_mask = 0;
|
|
if (ioctl(device_fd, SNDCTL_DSP_GETFMTS, &format_mask) == -1) {
|
|
format_mask = 0;
|
|
throw SndException("OSS formats");
|
|
}
|
|
}
|
|
|
|
void cSoundOSS::Format(int format)
|
|
{
|
|
play_format = format;
|
|
if (ioctl(device_fd, SNDCTL_DSP_SETFMT, &play_format) == -1) {
|
|
device_fd = -1;
|
|
formatok = false;
|
|
throw SndException("Unsupported snd card format");
|
|
}
|
|
formatok = true;
|
|
}
|
|
|
|
void cSoundOSS::Channels(int nuchannels)
|
|
{
|
|
channels = nuchannels;
|
|
if (ioctl(device_fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
|
|
device_fd = -1;
|
|
throw "Snd card channel request failed";
|
|
}
|
|
}
|
|
|
|
void cSoundOSS::Frequency(int frequency)
|
|
{
|
|
sample_frequency = frequency;
|
|
if (ioctl(device_fd, SNDCTL_DSP_SPEED, &sample_frequency) == -1) {
|
|
device_fd = -1;
|
|
throw SndException("Cannot set frequency");
|
|
}
|
|
}
|
|
|
|
int cSoundOSS::BufferSize( int seconds )
|
|
{
|
|
int bytes_per_channel = 0;
|
|
switch (play_format) {
|
|
case AFMT_MU_LAW:
|
|
case AFMT_A_LAW:
|
|
case AFMT_IMA_ADPCM:
|
|
bytes_per_channel = 0; /* format not supported by this program */
|
|
break;
|
|
case AFMT_S16_BE:
|
|
case AFMT_U16_LE:
|
|
case AFMT_U16_BE:
|
|
case AFMT_MPEG:
|
|
case AFMT_S16_LE:
|
|
bytes_per_channel = 2;
|
|
break;
|
|
case AFMT_U8:
|
|
case AFMT_S8:
|
|
bytes_per_channel = 1;
|
|
break;
|
|
}
|
|
return seconds * sample_frequency * bytes_per_channel * channels;
|
|
}
|
|
|
|
bool cSoundOSS::wait_till_finished()
|
|
{
|
|
if (ioctl(device_fd, SNDCTL_DSP_POST, 1) == -1 )
|
|
return false;
|
|
if (ioctl(device_fd, SNDCTL_DSP_SYNC, 0) == -1)
|
|
return false; /* format (or ioctl()) not supported by device */
|
|
return true; /* all sound has been played */
|
|
}
|
|
|
|
bool cSoundOSS::reset_device()
|
|
{
|
|
if (ioctl(device_fd, SNDCTL_DSP_RESET, 0) == -1) {
|
|
device_fd = -1;
|
|
return false; /* format (or ioctl()) not supported by device */
|
|
}
|
|
return 1; /* sounddevice has been reset */
|
|
}
|
|
|
|
int cSoundOSS::Write(unsigned char *buffer, int buffersize)
|
|
{
|
|
if (device_fd == -1)
|
|
return -1;
|
|
return write (device_fd, buffer, buffersize);
|
|
}
|
|
|
|
int cSoundOSS::Read(unsigned char *buffer, int buffersize)
|
|
{
|
|
if (device_fd == -1)
|
|
return -1;
|
|
|
|
return read (device_fd, buffer, buffersize);
|
|
}
|
|
|
|
int cSoundOSS::Read(double *buffer, int buffersize)
|
|
{
|
|
short int *ibuff = (short int *)cbuff;
|
|
int numread;
|
|
if (device_fd == -1)
|
|
return -1;
|
|
|
|
numread = Read(cbuff, buffersize * 4);
|
|
for (int i = 0; i < buffersize * 2; i++)
|
|
src_buffer[i] = ibuff[i] / MAXSC;
|
|
|
|
for (int i = 0; i < buffersize; i++)
|
|
buffer[i] = src_buffer[2*i];
|
|
|
|
if (rxppm != progdefaults.RX_corr) {
|
|
rxppm = progdefaults.RX_corr;
|
|
rx_src_data->src_ratio = 1.0/(1.0 + rxppm/1e6);
|
|
src_set_ratio ( rx_src_state, 1.0/(1.0 + rxppm/1e6));
|
|
}
|
|
|
|
if (capture) writeCapture( buffer, buffersize);
|
|
|
|
if (playback) {
|
|
readPlayback( buffer, buffersize);
|
|
if (progdefaults.EnableMixer) {
|
|
double vol = valRcvMixer->value();
|
|
for (int i = 0; i < buffersize; i++)
|
|
buffer[i] *= vol;
|
|
}
|
|
return buffersize;
|
|
}
|
|
|
|
if (rxppm == 0)
|
|
return buffersize;
|
|
|
|
// process using samplerate library
|
|
|
|
rx_src_data->data_in = src_buffer;
|
|
rx_src_data->input_frames = buffersize;
|
|
rx_src_data->data_out = snd_buffer;
|
|
rx_src_data->output_frames = SND_BUF_LEN;
|
|
rx_src_data->end_of_input = 0;
|
|
|
|
if ((numread = src_process(rx_src_state, rx_src_data)) != 0)
|
|
return -1;
|
|
|
|
numread = rx_src_data->output_frames_gen;
|
|
|
|
for (int i = 0; i < numread; i++)
|
|
buffer[i] = snd_buffer[2*i];
|
|
|
|
return numread;
|
|
|
|
}
|
|
|
|
int cSoundOSS::write_samples(double *buf, int count)
|
|
{
|
|
int retval;
|
|
short int *wbuff;
|
|
unsigned char *p;
|
|
|
|
if (generate) writeGenerate( buf, count );
|
|
|
|
if (device_fd == -1 || count <= 0)
|
|
return -1;
|
|
|
|
if (txppm != progdefaults.TX_corr) {
|
|
txppm = progdefaults.TX_corr;
|
|
tx_src_data->src_ratio = 1.0 + txppm/1e6;
|
|
src_set_ratio ( tx_src_state, 1.0 + txppm/1e6);
|
|
}
|
|
|
|
if (txppm == 0) {
|
|
wbuff = new short int[2*count];
|
|
p = (unsigned char *)wbuff;
|
|
for (int i = 0; i < count; i++) {
|
|
wbuff[2*i] = wbuff[2*i+1] = (short int)(buf[i] * maxsc);
|
|
}
|
|
count *= sizeof(short int);
|
|
retval = Write(p, 2*count);
|
|
delete [] wbuff;
|
|
}
|
|
else {
|
|
float *inbuf;
|
|
inbuf = new float[2*count];
|
|
int bufsize;
|
|
for (int i = 0; i < count; i++)
|
|
inbuf[2*i] = inbuf[2*i+1] = buf[i];
|
|
tx_src_data->data_in = inbuf;
|
|
tx_src_data->input_frames = count;
|
|
tx_src_data->data_out = src_buffer;
|
|
tx_src_data->output_frames = SND_BUF_LEN;
|
|
tx_src_data->end_of_input = 0;
|
|
|
|
if (src_process(tx_src_state, tx_src_data) != 0) {
|
|
delete [] inbuf;
|
|
return -1;
|
|
}
|
|
delete [] inbuf;
|
|
bufsize = tx_src_data->output_frames_gen;
|
|
wbuff = new short int[2*bufsize];
|
|
p = (unsigned char *)wbuff;
|
|
|
|
for (int i = 0; i < 2*bufsize; i++)
|
|
wbuff[i] = (short int)(src_buffer[i] * maxsc);
|
|
int num2write = bufsize * 2 * sizeof(short int);
|
|
|
|
retval = Write(p, num2write);
|
|
delete [] wbuff;
|
|
if (retval != num2write)
|
|
return -1;
|
|
retval = count;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
int cSoundOSS::write_stereo(double *bufleft, double *bufright, int count)
|
|
{
|
|
int retval;
|
|
short int *wbuff;
|
|
unsigned char *p;
|
|
|
|
if (generate) writeGenerate( bufleft, count );
|
|
|
|
if (device_fd == -1 || count <= 0)
|
|
return -1;
|
|
|
|
if (txppm != progdefaults.TX_corr) {
|
|
txppm = progdefaults.TX_corr;
|
|
tx_src_data->src_ratio = 1.0 + txppm/1e6;
|
|
src_set_ratio ( tx_src_state, 1.0 + txppm/1e6);
|
|
}
|
|
|
|
if (txppm == 0) {
|
|
wbuff = new short int[2*count];
|
|
p = (unsigned char *)wbuff;
|
|
for (int i = 0; i < count; i++) {
|
|
wbuff[2*i] = (short int)(bufleft[i] * maxsc);
|
|
wbuff[2*i + 1] = (short int)(bufright[i] * maxsc);
|
|
}
|
|
count *= sizeof(short int);
|
|
retval = Write(p, 2*count);
|
|
delete [] wbuff;
|
|
}
|
|
else {
|
|
float *inbuf;
|
|
inbuf = new float[2*count];
|
|
int bufsize;
|
|
for (int i = 0; i < count; i++) {
|
|
inbuf[2*i] = bufleft[i];
|
|
inbuf[2*i+1] = bufright[i];
|
|
}
|
|
tx_src_data->data_in = inbuf;
|
|
tx_src_data->input_frames = count;
|
|
tx_src_data->data_out = src_buffer;
|
|
tx_src_data->output_frames = SND_BUF_LEN;
|
|
tx_src_data->end_of_input = 0;
|
|
|
|
if (src_process(tx_src_state, tx_src_data) != 0) {
|
|
delete [] inbuf;
|
|
return -1;
|
|
}
|
|
delete [] inbuf;
|
|
bufsize = tx_src_data->output_frames_gen;
|
|
wbuff = new short int[2*bufsize];
|
|
p = (unsigned char *)wbuff;
|
|
|
|
for (int i = 0; i < 2*bufsize; i++)
|
|
wbuff[i] = (short int)(src_buffer[i] * maxsc);
|
|
|
|
int num2write = bufsize * 2 * sizeof(short int);
|
|
retval = Write(p, num2write);
|
|
delete [] wbuff;
|
|
if (retval != num2write)
|
|
return -1;
|
|
retval = count;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
#if USE_PORTAUDIO
|
|
|
|
bool cSoundPA::pa_init = false;
|
|
std::vector<const PaDeviceInfo*> cSoundPA::devs;
|
|
void cSoundPA::initialize(void)
|
|
{
|
|
if (pa_init)
|
|
return;
|
|
|
|
int err;
|
|
|
|
if ((err = Pa_Initialize()) != paNoError)
|
|
throw SndException(err);
|
|
pa_init = true;
|
|
|
|
PaDeviceIndex ndev = Pa_GetDeviceCount();
|
|
if ((ndev = Pa_GetDeviceCount()) < 0)
|
|
throw SndException(ndev);
|
|
if (ndev == 0)
|
|
throw SndException("No available audio devices");
|
|
|
|
devs.reserve(ndev);
|
|
for (PaDeviceIndex i = 0; i < ndev; i++)
|
|
devs.push_back(Pa_GetDeviceInfo(i));
|
|
|
|
}
|
|
void cSoundPA::terminate(void)
|
|
{
|
|
static_cast<void>(Pa_Terminate());
|
|
pa_init = false;
|
|
devs.clear();
|
|
}
|
|
const std::vector<const PaDeviceInfo*>& cSoundPA::devices(void)
|
|
{
|
|
return devs;
|
|
}
|
|
|
|
cSoundPA::cSoundPA(const char *dev)
|
|
: device(dev), stream(0), frames_per_buffer(paFramesPerBufferUnspecified),
|
|
req_sample_rate(0), dev_sample_rate(0), fbuf(0)
|
|
{
|
|
try {
|
|
rx_src_data = new SRC_DATA;
|
|
tx_src_data = new SRC_DATA;
|
|
}
|
|
catch (const std::bad_alloc& e) {
|
|
cerr << "Cannot create libsamplerate data structures\n";
|
|
throw;
|
|
}
|
|
|
|
try {
|
|
snd_buffer = new float[2 * SND_BUF_LEN];
|
|
src_buffer = new float[2 * SND_BUF_LEN];
|
|
fbuf = new float[2 * SND_BUF_LEN];
|
|
}
|
|
catch (const std::bad_alloc& e) {
|
|
cerr << "Cannot allocate libsamplerate buffers\n";
|
|
throw;
|
|
}
|
|
|
|
memset(snd_buffer, 0, 2 * SND_BUF_LEN);
|
|
memset(src_buffer, 0, 2 * SND_BUF_LEN);
|
|
memset(fbuf, 0, 2 * SND_BUF_LEN);
|
|
}
|
|
|
|
cSoundPA::~cSoundPA()
|
|
{
|
|
Close();
|
|
delete [] fbuf;
|
|
}
|
|
|
|
int cSoundPA::Open(int mode, int freq)
|
|
{
|
|
int old_sample_rate = (int)req_sample_rate;
|
|
req_sample_rate = sample_frequency = freq;
|
|
|
|
// Try to keep the stream open if we are using jack, or if we
|
|
// are in full duplex mode and the sample rate has not changed.
|
|
if (stream_active()) {
|
|
if (Pa_GetHostApiInfo((*idev)->hostApi)->type == paJACK) {
|
|
// If we have a new sample rate, we must reset the src data.
|
|
if (old_sample_rate != freq)
|
|
src_data_reset(1 << O_RDONLY | 1 << O_WRONLY);
|
|
return 0;
|
|
}
|
|
else if (full_duplex_device(*idev) && old_sample_rate == freq)
|
|
return 0;
|
|
}
|
|
|
|
Close();
|
|
init_stream();
|
|
#ifndef NDEBUG
|
|
if (dev_sample_rate != req_sample_rate)
|
|
cerr << "PA_debug: resampling " << dev_sample_rate
|
|
<< " <-> " << req_sample_rate << endl;
|
|
#endif
|
|
|
|
mode = full_duplex() ? 1 << O_RDONLY | 1 << O_WRONLY : 1 << mode;
|
|
if (!(mode & 1 << O_WRONLY))
|
|
stream_params[STREAM_OUT] = NULL;
|
|
if (!(mode & 1 << O_RDONLY))
|
|
stream_params[STREAM_IN] = NULL;
|
|
src_data_reset(mode);
|
|
|
|
start_stream();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cSoundPA::Close(void)
|
|
{
|
|
if (!stream_active())
|
|
return;
|
|
|
|
int err;
|
|
|
|
if ((err = Pa_StopStream(stream)) != paNoError)
|
|
pa_perror(err, "Pa_StopStream");
|
|
if ((err = Pa_CloseStream(stream)) != paNoError)
|
|
pa_perror(err, "Pa_CloseStream");
|
|
|
|
stream = 0;
|
|
}
|
|
|
|
int cSoundPA::Read(double *buf, int count)
|
|
{
|
|
int ncount = (int)floor(MIN(count, SND_BUF_LEN) / rx_src_data->src_ratio);
|
|
|
|
int err;
|
|
static int retries = 0;
|
|
if ((err = Pa_ReadStream(stream, fbuf, ncount)) != paNoError) {
|
|
pa_perror(err, "Pa_ReadStream");
|
|
switch (err) {
|
|
case paInputOverflowed:
|
|
return adjust_stream() ? Read(buf, count) : 0;
|
|
case paUnanticipatedHostError:
|
|
if (Pa_GetHostApiInfo((*idev)->hostApi)->type == paOSS && retries++ < 8) {
|
|
cerr << "Retrying read\n";
|
|
return Read(buf, count);
|
|
}
|
|
else
|
|
cerr << "Giving up\n";
|
|
// fall through
|
|
default:
|
|
throw SndException(err);
|
|
}
|
|
}
|
|
retries = 0;
|
|
|
|
if (capture)
|
|
writeCapture(buf, count);
|
|
if (playback) {
|
|
readPlayback(buf, count);
|
|
if (progdefaults.EnableMixer) {
|
|
double vol = valRcvMixer->value();
|
|
for (int i = 0; i < count; i++)
|
|
buf[i] *= vol;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
float *rbuf = fbuf;
|
|
if (req_sample_rate != dev_sample_rate || progdefaults.RX_corr != 0) {
|
|
resample(1 << O_RDONLY, rbuf, ncount, count);
|
|
rbuf = rx_src_data->data_out;
|
|
count = rx_src_data->output_frames_gen;
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
buf[i] = rbuf[2*i];
|
|
|
|
return count;
|
|
}
|
|
|
|
int cSoundPA::write_samples(double *buf, int count)
|
|
{
|
|
if (generate)
|
|
writeGenerate(buf, count);
|
|
|
|
for (int i = 0; i < count; i++)
|
|
fbuf[2*i] = fbuf[2*i + 1] = buf[i];
|
|
|
|
float *wbuf = fbuf;
|
|
if (req_sample_rate != dev_sample_rate || progdefaults.TX_corr != 0) {
|
|
resample(1 << O_WRONLY, wbuf, count);
|
|
wbuf = tx_src_data->data_out;
|
|
count = tx_src_data->output_frames_gen;
|
|
}
|
|
|
|
int err;
|
|
static int retries = 0;
|
|
if ((err = Pa_WriteStream(stream, wbuf, count)) != paNoError) {
|
|
pa_perror(err, "Pa_WriteStream");
|
|
switch (err) {
|
|
case paOutputUnderflowed:
|
|
return adjust_stream() ? write_samples(buf, count) : 0;
|
|
case paUnanticipatedHostError:
|
|
if (Pa_GetHostApiInfo((*idev)->hostApi)->type == paOSS && retries++ < 8) {
|
|
cerr << "Retrying write\n";
|
|
return write_samples(buf, count);
|
|
}
|
|
else
|
|
cerr << "Giving up\n";
|
|
// fall through
|
|
default:
|
|
throw SndException(err);
|
|
}
|
|
}
|
|
retries = 0;
|
|
|
|
return count;
|
|
}
|
|
|
|
int cSoundPA::write_stereo(double *bufleft, double *bufright, int count)
|
|
{
|
|
if (generate)
|
|
writeGenerate(bufleft, count);
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
fbuf[2*i] = bufleft[i];
|
|
fbuf[2*i + 1] = bufright[i];
|
|
}
|
|
|
|
float *wbuf = fbuf;
|
|
if (req_sample_rate != dev_sample_rate || progdefaults.TX_corr != 0) {
|
|
resample(1 << O_WRONLY, wbuf, count);
|
|
wbuf = tx_src_data->data_out;
|
|
count = tx_src_data->output_frames_gen;
|
|
}
|
|
|
|
int err;
|
|
static int retries = 0;
|
|
if ((err = Pa_WriteStream(stream, wbuf, count)) != paNoError) {
|
|
pa_perror(err, "Pa_WriteStream");
|
|
switch (err) {
|
|
case paOutputUnderflowed:
|
|
return adjust_stream() ? write_stereo(bufleft, bufright, count) : 0;
|
|
case paUnanticipatedHostError:
|
|
if (Pa_GetHostApiInfo((*idev)->hostApi)->type == paOSS && retries++ < 8) {
|
|
cerr << "Retrying write\n";
|
|
return write_stereo(bufleft, bufright, count);
|
|
}
|
|
else
|
|
cerr << "Giving up\n";
|
|
// fall through
|
|
default:
|
|
throw SndException(err);
|
|
}
|
|
|
|
throw SndException(err);
|
|
}
|
|
retries = 0;
|
|
|
|
return count;
|
|
}
|
|
|
|
bool cSoundPA::full_duplex(void)
|
|
{
|
|
extern bool pa_allow_full_duplex;
|
|
return (pa_allow_full_duplex && full_duplex_device(*idev)) ||
|
|
Pa_GetHostApiInfo((*idev)->hostApi)->type == paJACK;
|
|
}
|
|
|
|
void cSoundPA::src_data_reset(int mode)
|
|
{
|
|
int err;
|
|
if (mode & 1 << O_RDONLY) {
|
|
if (rx_src_state)
|
|
src_delete(rx_src_state);
|
|
rx_src_state = src_new(SRC_SINC_FASTEST, 2, &err);
|
|
if (!rx_src_state)
|
|
throw SndException(src_strerror(err));
|
|
rx_src_data->src_ratio = req_sample_rate / (dev_sample_rate * (1.0 + rxppm / 1e6));
|
|
}
|
|
if (mode & 1 << O_WRONLY) {
|
|
if (tx_src_state)
|
|
src_delete(tx_src_state);
|
|
tx_src_state = src_new(SRC_SINC_FASTEST, 2, &err);
|
|
if (!tx_src_state)
|
|
throw SndException(src_strerror(err));
|
|
tx_src_data->src_ratio = dev_sample_rate * (1.0 + txppm / 1e6) / req_sample_rate;
|
|
}
|
|
}
|
|
|
|
void cSoundPA::resample(int mode, float *buf, int count, int max)
|
|
{
|
|
if (mode & 1 << O_RDONLY) {
|
|
if (rxppm != progdefaults.RX_corr) {
|
|
rxppm = progdefaults.RX_corr;
|
|
rx_src_data->src_ratio = req_sample_rate
|
|
/ dev_sample_rate
|
|
* (1.0 + rxppm / 1e6);
|
|
src_set_ratio(rx_src_state, rx_src_data->src_ratio);
|
|
}
|
|
|
|
rx_src_data->data_in = buf;
|
|
rx_src_data->input_frames = count;
|
|
rx_src_data->data_out = snd_buffer;
|
|
rx_src_data->output_frames = max ? max : SND_BUF_LEN;
|
|
rx_src_data->end_of_input = 0;
|
|
|
|
src_process(rx_src_state, rx_src_data);
|
|
}
|
|
else if (mode & 1 << O_WRONLY) {
|
|
if (txppm != progdefaults.TX_corr) {
|
|
txppm = progdefaults.TX_corr;
|
|
tx_src_data->src_ratio = dev_sample_rate
|
|
* (1.0 + txppm / 1e6)
|
|
/ req_sample_rate;
|
|
src_set_ratio(tx_src_state, tx_src_data->src_ratio);
|
|
}
|
|
|
|
tx_src_data->data_in = buf;
|
|
tx_src_data->input_frames = count;
|
|
tx_src_data->data_out = src_buffer;
|
|
tx_src_data->output_frames = max ? max : SND_BUF_LEN;
|
|
tx_src_data->end_of_input = 0;
|
|
|
|
src_process(tx_src_state, tx_src_data);
|
|
}
|
|
}
|
|
|
|
void cSoundPA::init_stream(void)
|
|
{
|
|
#ifndef NDEBUG
|
|
cerr << "PA_debug: looking for \"" << device << "\"\n";
|
|
#endif
|
|
for (idev = devs.begin(); idev != devs.end(); ++idev)
|
|
if (device == (*idev)->name)
|
|
break;
|
|
if (idev == devs.end()) {
|
|
cerr << "PA_debug: could not find device \"" << device << "\"\n";
|
|
idev = devs.begin();
|
|
}
|
|
PaDeviceIndex idx = idev - devs.begin();
|
|
|
|
#ifndef NDEBUG
|
|
cerr << "PA_debug: using device:"
|
|
<< "\n index: " << idx
|
|
<< "\n name: " << (*idev)->name
|
|
<< "\n hostAPI: " << Pa_GetHostApiInfo((*idev)->hostApi)->name
|
|
<< "\n maxInputChannels: " << (*idev)->maxInputChannels
|
|
<< "\n maxOutputChannels: " << (*idev)->maxOutputChannels
|
|
<< "\n defaultLowInputLatency: " << (*idev)->defaultLowInputLatency
|
|
<< "\n defaultHighInputLatency: " << (*idev)->defaultHighInputLatency
|
|
<< "\n defaultLowOutputLatency: " << (*idev)->defaultLowOutputLatency
|
|
<< "\n defaultHighOutputLatency: " << (*idev)->defaultHighOutputLatency
|
|
<< "\n defaultSampleRate: " << (*idev)->defaultSampleRate
|
|
<< boolalpha
|
|
<< "\n isInputOnlyDevice: " << ((*idev)->maxOutputChannels == 0)
|
|
<< "\n isOutputOnlyDevice: " << ((*idev)->maxInputChannels == 0)
|
|
<< "\n isFullDuplexDevice: " << full_duplex_device(*idev)
|
|
<< "\n isSystemDefaultInputDevice: " << (idx == Pa_GetDefaultInputDevice())
|
|
<< "\n isSystemDefaultOutputDevice: " << (idx == Pa_GetDefaultOutputDevice())
|
|
<< "\n isHostApiDefaultInputDevice: " << (idx == Pa_GetHostApiInfo((*idev)->hostApi)->defaultInputDevice)
|
|
<< "\n isHostApiDefaultOutputDevice: " << (idx == Pa_GetHostApiInfo((*idev)->hostApi)->defaultOutputDevice)
|
|
<< "\n\n";
|
|
#endif
|
|
|
|
// we are unlikely to have an output-only device
|
|
if ((*idev)->maxInputChannels == 0)
|
|
throw SndException(EBUSY);
|
|
|
|
in_params.device = idx;
|
|
in_params.channelCount = 2;
|
|
in_params.sampleFormat = paFloat32;
|
|
in_params.suggestedLatency = (*idev)->defaultHighInputLatency;
|
|
in_params.hostApiSpecificStreamInfo = NULL;
|
|
stream_params[STREAM_IN] = &in_params;
|
|
|
|
out_params.device = idx;
|
|
out_params.channelCount = 2;
|
|
out_params.sampleFormat = paFloat32;
|
|
out_params.suggestedLatency = (*idev)->defaultHighOutputLatency;
|
|
out_params.hostApiSpecificStreamInfo = NULL;
|
|
stream_params[STREAM_OUT] = &out_params;
|
|
|
|
dev_sample_rate = find_srate();
|
|
|
|
max_frames_per_buffer = ceil2(MIN(SND_BUF_LEN, (unsigned)(SCBLOCKSIZE *
|
|
dev_sample_rate / req_sample_rate)));
|
|
#ifndef NDEBUG
|
|
cerr << "PA_debug: max_frames_per_buffer = " << max_frames_per_buffer << endl;
|
|
#endif
|
|
extern int pa_frames_per_buffer;
|
|
if (pa_frames_per_buffer)
|
|
frames_per_buffer = pa_frames_per_buffer;
|
|
}
|
|
|
|
void cSoundPA::start_stream(void)
|
|
{
|
|
int err;
|
|
|
|
err = Pa_OpenStream(&stream, stream_params[STREAM_IN], stream_params[STREAM_OUT],
|
|
dev_sample_rate, frames_per_buffer, paNoFlag, NULL, NULL);
|
|
if (err != paNoError)
|
|
throw SndException(err);
|
|
if ((err = Pa_StartStream(stream)) != paNoError) {
|
|
Close();
|
|
throw SndException(err);
|
|
}
|
|
}
|
|
|
|
bool cSoundPA::stream_active(void)
|
|
{
|
|
if (!stream)
|
|
return false;
|
|
|
|
int err;
|
|
|
|
if ((err = Pa_IsStreamActive(stream)) < 0)
|
|
throw SndException(err);
|
|
|
|
return err == 1;
|
|
}
|
|
|
|
bool cSoundPA::full_duplex_device(const PaDeviceInfo* dev)
|
|
{
|
|
return dev->maxInputChannels > 0 && dev->maxOutputChannels > 0;
|
|
}
|
|
|
|
bool cSoundPA::adjust_stream(void)
|
|
{
|
|
if (frames_per_buffer == max_frames_per_buffer)
|
|
return false;
|
|
|
|
if (frames_per_buffer != paFramesPerBufferUnspecified)
|
|
frames_per_buffer *= 2;
|
|
else
|
|
frames_per_buffer = SCBLOCKSIZE;
|
|
|
|
if (!powerof2(frames_per_buffer))
|
|
frames_per_buffer = ceil2(frames_per_buffer);
|
|
|
|
frames_per_buffer = MIN(max_frames_per_buffer, frames_per_buffer);
|
|
|
|
cerr << "PA_debug: adjusting frames_per_buffer to "
|
|
<< frames_per_buffer << endl;
|
|
Close();
|
|
start_stream();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Determine the sample rate that we will use. We try the modem's rate
|
|
// first and fall back to the device's default rate. If there is a user
|
|
// setting we just return that without making any checks.
|
|
double cSoundPA::find_srate(void)
|
|
{
|
|
switch (progdefaults.sample_rate) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
return (*idev)->defaultSampleRate;
|
|
default:
|
|
return progdefaults.sample_rate;
|
|
}
|
|
|
|
double srates[] = { req_sample_rate, (*idev)->defaultSampleRate };
|
|
int err;
|
|
for (size_t i = 0; i < sizeof(srates)/sizeof(srates[0]); i++) {
|
|
#ifndef NDEBUG
|
|
cerr << "PA_debug: trying " << srates[i] << " Hz" << endl;
|
|
#endif
|
|
if ((err = Pa_IsFormatSupported(&in_params, &out_params, srates[i])) == paFormatIsSupported)
|
|
return srates[i];
|
|
#ifndef NDEBUG
|
|
else
|
|
pa_perror(err, "Pa_IsFormatSupported");
|
|
#endif
|
|
}
|
|
|
|
throw SndException(err);
|
|
return -1;
|
|
}
|
|
|
|
void cSoundPA::pa_perror(int err, const char* str)
|
|
{
|
|
if (str)
|
|
cerr << str << ": " << Pa_GetErrorText(err) << '\n';
|
|
|
|
if (err == paUnanticipatedHostError) {
|
|
const PaHostErrorInfo* hosterr = Pa_GetLastHostErrorInfo();
|
|
PaHostApiIndex i = Pa_HostApiTypeIdToHostApiIndex(hosterr->hostApiType);
|
|
|
|
if (i < 0) { // PA failed without setting its "last host error" info. Sigh...
|
|
cerr << "Host API error info not available\n";
|
|
if (Pa_GetHostApiInfo((*idev)->hostApi)->type == paOSS && errno)
|
|
cerr << "Possible OSS error " << errno << ": "
|
|
<< strerror(errno) << '\n';
|
|
}
|
|
else
|
|
cerr << Pa_GetHostApiInfo(i)->name << " error "
|
|
<< hosterr->errorCode << ": " << hosterr->errorText << '\n';
|
|
}
|
|
}
|
|
|
|
#endif // USE_PORTAUDIO
|