kopia lustrzana https://github.com/jamescoxon/dl-fldigi
2241 wiersze
58 KiB
C++
2241 wiersze
58 KiB
C++
// ----------------------------------------------------------------------------
|
|
//
|
|
// sound.cxx
|
|
//
|
|
// Copyright (C) 2006-2013
|
|
// Dave Freese, W1HKJ
|
|
//
|
|
// Copyright (C) 2007-2010
|
|
// 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include <config.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cerrno>
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <semaphore.h>
|
|
#include <limits.h>
|
|
|
|
#if USE_OSS
|
|
# include <sys/ioctl.h>
|
|
# if defined(__OpenBSD__)
|
|
# include <soundcard.h>
|
|
# else
|
|
# include <sys/soundcard.h>
|
|
# endif
|
|
#endif
|
|
#include <math.h>
|
|
|
|
#if HAVE_DLOPEN
|
|
# include <dlfcn.h>
|
|
#endif
|
|
|
|
#include "gettext.h"
|
|
#include "sound.h"
|
|
#include "configuration.h"
|
|
#include "status.h"
|
|
#include "fileselect.h"
|
|
#include "trx.h"
|
|
#include "fl_digi.h"
|
|
#include "threads.h"
|
|
#include "timeops.h"
|
|
#include "ringbuffer.h"
|
|
#include "debug.h"
|
|
#include "qrunner.h"
|
|
#include "icons.h"
|
|
#include "macros.h"
|
|
#include "util.h"
|
|
|
|
#include "estrings.h"
|
|
|
|
#define SND_BUF_LEN 65536
|
|
#define SND_RW_LEN (8 * SND_BUF_LEN)
|
|
|
|
// We never write duplicate/QSK/PTT tone/PseudoFSK data to the sound files
|
|
#define SNDFILE_CHANNELS 1
|
|
|
|
int sndfile_samplerate[7] = {8000, 11025, 16000, 22050, 24000, 44100, 48000};
|
|
|
|
using namespace std;
|
|
|
|
LOG_FILE_SOURCE(debug::LOG_AUDIO);
|
|
|
|
SoundBase::SoundBase()
|
|
: sample_frequency(0),
|
|
txppm(progdefaults.TX_corr), rxppm(progdefaults.RX_corr),
|
|
tx_src_state(0), rx_src_state(0),
|
|
wrt_buffer(new double[SND_BUF_LEN]),
|
|
#if USE_SNDFILE
|
|
ofCapture(0), ifPlayback(0), ofGenerate(0),
|
|
#endif
|
|
capture(false), playback(false), generate(false)
|
|
{
|
|
memset(wrt_buffer, 0, SND_BUF_LEN * sizeof(*wrt_buffer));
|
|
|
|
#if USE_SNDFILE
|
|
int err;
|
|
writ_src_data = new SRC_DATA;
|
|
play_src_data = new SRC_DATA;
|
|
|
|
writ_src_state = src_new(progdefaults.sample_converter, 1, &err);
|
|
if (writ_src_state == 0)
|
|
throw SndException(src_strerror(err));
|
|
|
|
play_src_state = src_new(progdefaults.sample_converter, 1, &err);
|
|
if (play_src_state == 0)
|
|
throw SndException(src_strerror(err));
|
|
|
|
if (play_src_state == 0)
|
|
throw SndException(src_strerror(err));
|
|
src_out_buffer = new float [SND_RW_LEN];
|
|
if (!src_out_buffer)
|
|
throw SndException(src_strerror(err));
|
|
src_inp_buffer = new float [SND_RW_LEN];
|
|
if (!src_inp_buffer)
|
|
throw SndException(src_strerror(err));
|
|
modem_wr_sr = modem_play_sr = 0;
|
|
inp_pointer = src_out_buffer;
|
|
#endif
|
|
}
|
|
|
|
SoundBase::~SoundBase()
|
|
{
|
|
delete [] wrt_buffer;
|
|
|
|
#if USE_SNDFILE
|
|
if (ofGenerate)
|
|
sf_close(ofGenerate);
|
|
if (ofCapture)
|
|
sf_close(ofCapture);
|
|
if (ifPlayback)
|
|
sf_close(ifPlayback);
|
|
delete writ_src_data;
|
|
delete play_src_data;
|
|
delete [] src_out_buffer;
|
|
delete [] src_inp_buffer;
|
|
#endif
|
|
}
|
|
|
|
#if USE_SNDFILE
|
|
void SoundBase::get_file_params(const char* def_fname, const char** fname, int* format)
|
|
{
|
|
std::string filters = _("Waveform Audio Format\t*.wav\n" "AU\t*.{au,snd}\n");
|
|
int nfilt = 2;
|
|
if (format_supported(SF_FORMAT_FLAC | SF_FORMAT_PCM_16)) {
|
|
filters += _("Free Lossless Audio Codec\t*.flac");
|
|
nfilt++;
|
|
}
|
|
|
|
int fsel;
|
|
if (strstr(def_fname, "playback"))
|
|
*fname = FSEL::select(_("Audio file"), filters.c_str(), def_fname, &fsel);
|
|
else
|
|
*fname = FSEL::saveas(_("Audio file"), filters.c_str(), def_fname, &fsel);
|
|
if (!*fname)
|
|
return;
|
|
|
|
if (fsel >= nfilt) // "Default" save-as type on OS X
|
|
fsel = 0;
|
|
switch (fsel) {
|
|
case 0:
|
|
*format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
|
|
break;
|
|
case 1:
|
|
*format = SF_FORMAT_AU | SF_FORMAT_FLOAT | SF_ENDIAN_CPU;
|
|
break;
|
|
case 2:
|
|
*format = SF_FORMAT_FLAC | SF_FORMAT_PCM_16;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int SoundBase::Capture(bool val)
|
|
{
|
|
if (!val) {
|
|
if (ofCapture) {
|
|
int err;
|
|
if ((err = sf_close(ofCapture)) != 0)
|
|
LOG_ERROR("sf_close error: %s", sf_error_number(err));
|
|
ofCapture = 0;
|
|
}
|
|
capture = false;
|
|
return 1;
|
|
}
|
|
|
|
const char* fname;
|
|
int format;
|
|
get_file_params("capture.wav", &fname, &format);
|
|
if (!fname)
|
|
return 0;
|
|
|
|
// frames (ignored), freq, channels, format, sections (ignored), seekable (ignored)
|
|
SF_INFO info = { 0, sndfile_samplerate[progdefaults.wavSampleRate], SNDFILE_CHANNELS, format, 0, 0 };
|
|
if ((ofCapture = sf_open(fname, SFM_WRITE, &info)) == NULL) {
|
|
LOG_ERROR("Could not write %s:%s", fname, sf_strerror(NULL) );
|
|
return 0;
|
|
}
|
|
if (sf_command(ofCapture, SFC_SET_UPDATE_HEADER_AUTO, NULL, SF_TRUE) != SF_TRUE)
|
|
LOG_ERROR("ofCapture update header command failed: %s", sf_strerror(ofCapture));
|
|
tag_file(ofCapture, "Captured audio");
|
|
|
|
capture = true;
|
|
return 1;
|
|
}
|
|
|
|
int SoundBase::Generate(bool val)
|
|
{
|
|
if (!val) {
|
|
if (ofGenerate) {
|
|
int err;
|
|
if ((err = sf_close(ofGenerate)) != 0)
|
|
LOG_ERROR("sf_close error: %s", sf_error_number(err));
|
|
ofGenerate = 0;
|
|
}
|
|
generate = false;
|
|
return 1;
|
|
}
|
|
|
|
const char* fname;
|
|
int format;
|
|
get_file_params("generate.wav", &fname, &format);
|
|
if (!fname)
|
|
return 0;
|
|
|
|
SF_INFO info = { 0, sndfile_samplerate[progdefaults.wavSampleRate], SNDFILE_CHANNELS, format, 0, 0 };
|
|
if ((ofGenerate = sf_open(fname, SFM_WRITE, &info)) == NULL) {
|
|
LOG_ERROR("Could not write %s", fname);
|
|
return 0;
|
|
}
|
|
if (sf_command(ofGenerate, SFC_SET_UPDATE_HEADER_AUTO, NULL, SF_TRUE) != SF_TRUE)
|
|
LOG_ERROR("ofGenerate update header command failed: %s", sf_strerror(ofGenerate));
|
|
tag_file(ofGenerate, "Generated audio");
|
|
|
|
generate = true;
|
|
|
|
modem_wr_sr = sample_frequency;
|
|
writ_src_data->src_ratio = 1.0 * sndfile_samplerate[progdefaults.wavSampleRate] / modem_wr_sr;
|
|
src_set_ratio(writ_src_state, writ_src_data->src_ratio);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int SoundBase::Playback(bool val)
|
|
{
|
|
if (!val) {
|
|
if (ifPlayback) {
|
|
int err;
|
|
if ((err = sf_close(ifPlayback)) != 0)
|
|
LOG_ERROR("sf_close error: %s", sf_error_number(err));
|
|
ifPlayback = 0;
|
|
}
|
|
playback = false;
|
|
return 1;
|
|
}
|
|
const char* fname;
|
|
int format;
|
|
get_file_params("playback.wav", &fname, &format);
|
|
if (!fname)
|
|
return -1;
|
|
|
|
play_info.frames = 0;
|
|
play_info.samplerate = 0;
|
|
play_info.channels = 0;
|
|
play_info.format = 0;
|
|
play_info.sections = 0;
|
|
play_info.seekable = 0;
|
|
|
|
if ((ifPlayback = sf_open(fname, SFM_READ, &play_info)) == NULL) {
|
|
LOG_ERROR("Could not read %s:%s", fname, sf_strerror(NULL) );
|
|
return -2;
|
|
}
|
|
LOG_VERBOSE("wav file stats:\n\
|
|
frames : %d\n\
|
|
samplerate : %d\n\
|
|
channels : %d\n\
|
|
format : %d\n\
|
|
sections : %d\n\
|
|
seekable : %d",
|
|
static_cast<unsigned int>(play_info.frames),
|
|
play_info.samplerate,
|
|
play_info.channels,
|
|
play_info.format,
|
|
play_info.sections,
|
|
play_info.seekable);
|
|
|
|
// this restriction needs to be removed!
|
|
if (play_info.channels != 1) {
|
|
sf_close(ifPlayback);
|
|
return -3;
|
|
}
|
|
|
|
modem_play_sr = sample_frequency;
|
|
play_src_data->src_ratio = 1.0 * modem_play_sr / play_info.samplerate;
|
|
src_set_ratio(play_src_state, play_src_data->src_ratio);
|
|
LOG_VERBOSE("src ratio %f", play_src_data->src_ratio);
|
|
|
|
progdefaults.loop_playback = fl_choice2(_("Playback continuous loop?"), _("No"), _("Yes"), NULL);
|
|
|
|
playback = true;
|
|
new_playback = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// read_file
|
|
// can be simplified from the equivalent read audio stream
|
|
// source sr is arbitrary, requested is either 8000 or 11025 depending
|
|
// on the modem in use
|
|
// read from file and resample until a "count" number of converted samples
|
|
// is available, or until at the end of the input file
|
|
// ---------------------------------------------------------------------
|
|
sf_count_t SoundBase::read_file(SNDFILE* file, float* buf, size_t count)
|
|
{
|
|
sf_count_t r = 0, rd_count = 0;
|
|
int err = 0;
|
|
|
|
if (new_playback || modem_play_sr != sample_frequency) {
|
|
modem_play_sr = sample_frequency;
|
|
play_src_data->src_ratio = 1.0 * modem_play_sr / play_info.samplerate;
|
|
src_set_ratio(play_src_state, play_src_data->src_ratio);
|
|
LOG_VERBOSE("src ratio %f", play_src_data->src_ratio);
|
|
new_playback = true;
|
|
}
|
|
|
|
while ( static_cast<size_t>(inp_pointer - src_out_buffer) < count) {
|
|
if (new_playback) {
|
|
memset(src_inp_buffer, 0, 1024 * sizeof(float));
|
|
new_playback = false;
|
|
rd_count = 1024;
|
|
} else
|
|
rd_count = sf_readf_float(file, src_inp_buffer, 1024);
|
|
if (!rd_count) break;
|
|
play_src_data->data_in = src_inp_buffer;
|
|
play_src_data->input_frames = rd_count;
|
|
play_src_data->data_out = inp_pointer;
|
|
play_src_data->output_frames = SND_RW_LEN - (inp_pointer - src_out_buffer);
|
|
play_src_data->end_of_input = 0;
|
|
|
|
if ((err = src_process(play_src_state, play_src_data)) != 0)
|
|
throw SndException(src_strerror(err));
|
|
|
|
inp_pointer += play_src_data->output_frames_gen;
|
|
}
|
|
if ( static_cast<size_t>(inp_pointer - src_out_buffer) >= count) {
|
|
memcpy(buf, src_out_buffer, count * sizeof(float));
|
|
memmove(src_out_buffer, src_out_buffer + count, (SND_RW_LEN - count) * sizeof(float));
|
|
inp_pointer -= count;
|
|
r = count;
|
|
}
|
|
|
|
if (r == 0) {
|
|
src_reset (play_src_state);
|
|
inp_pointer = src_out_buffer;
|
|
if (!progdefaults.loop_playback) {
|
|
Playback(0);
|
|
bHighSpeed = false;
|
|
REQ(reset_mnuPlayback);
|
|
} else {
|
|
memset(buf, count, sizeof(*buf));
|
|
sf_seek(file, 0, SEEK_SET);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// write_file
|
|
// All sound buffer data is resampled to a specified sample rate
|
|
// progdefaults.wavSampleRate
|
|
// resultant data (left channel only) is written to a wav file
|
|
//----------------------------------------------------------------------
|
|
void SoundBase::write_file(SNDFILE* file, float* buf, size_t count)
|
|
{
|
|
int err;
|
|
if (modem_wr_sr != sample_frequency) {
|
|
modem_wr_sr = sample_frequency;
|
|
writ_src_data->src_ratio = 1.0 * sndfile_samplerate[progdefaults.wavSampleRate] / modem_wr_sr;
|
|
src_set_ratio(writ_src_state, writ_src_data->src_ratio);
|
|
}
|
|
writ_src_data->data_in = buf;
|
|
writ_src_data->input_frames = count;
|
|
writ_src_data->data_out = src_out_buffer;
|
|
writ_src_data->output_frames = SND_RW_LEN;
|
|
writ_src_data->end_of_input = 0;
|
|
|
|
if ((err = src_process(writ_src_state, writ_src_data)) != 0) {
|
|
throw SndException(src_strerror(err));
|
|
}
|
|
|
|
size_t output_size = writ_src_data->output_frames_gen;
|
|
|
|
if (output_size) {
|
|
for (size_t i = 0; i < output_size; i++)
|
|
writ_src_data->data_out[i] *= 0.9;
|
|
sf_writef_float(file, writ_src_data->data_out, output_size);
|
|
}
|
|
return;
|
|
|
|
}
|
|
|
|
void SoundBase::write_file(SNDFILE* file, double* buf, size_t count)
|
|
{
|
|
float *outbuf = new float[count];
|
|
for (size_t i = 0; i < count; i++)
|
|
outbuf[i] = buf[i];
|
|
write_file(file, outbuf, count);
|
|
delete [] outbuf;
|
|
return;
|
|
}
|
|
|
|
bool SoundBase::format_supported(int format)
|
|
{
|
|
SF_INFO info = {
|
|
0,
|
|
sndfile_samplerate[progdefaults.wavSampleRate],
|
|
SNDFILE_CHANNELS,
|
|
format,
|
|
0,
|
|
0 };
|
|
FILE* f;
|
|
if ((f = tmpfile()) == NULL)
|
|
return false;
|
|
SNDFILE* sndf = sf_open_fd(fileno(f), SFM_WRITE, &info, SF_FALSE);
|
|
fclose(f);
|
|
if (sndf)
|
|
sf_close(sndf);
|
|
return sndf;
|
|
}
|
|
|
|
void SoundBase::tag_file(SNDFILE *sndfile, const char *title)
|
|
{
|
|
int err;
|
|
if ((err = sf_set_string(sndfile, SF_STR_TITLE, title)) != 0) {
|
|
LOG_VERBOSE("sf_set_string STR_TITLE: %s", sf_error_number(err));
|
|
return;
|
|
}
|
|
|
|
sf_set_string(sndfile, SF_STR_COPYRIGHT, progdefaults.myName.c_str());
|
|
sf_set_string(sndfile, SF_STR_SOFTWARE, PACKAGE_NAME "-" PACKAGE_VERSION);
|
|
sf_set_string(sndfile, SF_STR_ARTIST, progdefaults.myCall.c_str());
|
|
|
|
char s[64];
|
|
snprintf(s, sizeof(s), "%s freq=%s",
|
|
active_modem->get_mode_name(), inpFreq->value());
|
|
sf_set_string(sndfile, SF_STR_COMMENT, s);
|
|
|
|
time_t t = time(0);
|
|
struct tm zt;
|
|
(void)gmtime_r(&t, &zt);
|
|
if (strftime(s, sizeof(s), "%Y-%m-%dT%H:%M:%Sz", &zt) > 0)
|
|
sf_set_string(sndfile, SF_STR_DATE, s);
|
|
}
|
|
#endif // USE_SNDFILE
|
|
|
|
|
|
#if USE_OSS
|
|
|
|
#define MAXSC 32767.0f
|
|
#define maxsc 32000.0
|
|
|
|
SoundOSS::SoundOSS(const char *dev ) {
|
|
device = dev;
|
|
cbuff = 0;
|
|
try {
|
|
Open(O_RDONLY);
|
|
getVersion();
|
|
getCapabilities();
|
|
getFormats();
|
|
Close();
|
|
}
|
|
catch (const SndException& e) {
|
|
LOG_ERROR("device %s error: %s", device.c_str(), e.what());
|
|
}
|
|
|
|
snd_buffer = new float [2 * SND_BUF_LEN];
|
|
src_buffer = new float [2 * SND_BUF_LEN];
|
|
cbuff = new unsigned char [4 * SND_BUF_LEN];
|
|
|
|
memset(snd_buffer, 0, 2 * SND_BUF_LEN * sizeof(*snd_buffer));
|
|
memset(src_buffer, 0, 2 * SND_BUF_LEN * sizeof(*src_buffer));
|
|
memset(cbuff, 0, 4 * SND_BUF_LEN * sizeof(*cbuff));
|
|
|
|
tx_src_data = new SRC_DATA;
|
|
rx_src_data = new SRC_DATA;
|
|
|
|
int err;
|
|
rx_src_state = src_new(progdefaults.sample_converter, 2, &err);
|
|
if (rx_src_state == 0)
|
|
throw SndException(src_strerror(err));
|
|
|
|
tx_src_state = src_new(progdefaults.sample_converter, 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);
|
|
}
|
|
|
|
SoundOSS::~SoundOSS()
|
|
{
|
|
Close();
|
|
|
|
delete tx_src_data;
|
|
delete rx_src_data;
|
|
|
|
if (rx_src_state)
|
|
src_delete(rx_src_state);
|
|
if (tx_src_state)
|
|
src_delete(tx_src_state);
|
|
|
|
delete [] snd_buffer;
|
|
delete [] src_buffer;
|
|
delete [] cbuff;
|
|
}
|
|
|
|
void SoundOSS::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 SoundOSS::Open(int md, int freq)
|
|
{
|
|
Close();
|
|
|
|
mode = md;
|
|
try {
|
|
int oflags = md;
|
|
# ifdef HAVE_O_CLOEXEC
|
|
oflags = oflags | O_CLOEXEC;
|
|
# endif
|
|
|
|
device_fd = open(device.c_str(), oflags, 0);
|
|
if (device_fd == -1)
|
|
throw SndException(errno);
|
|
Format(AFMT_S16_LE); // default: 16 bit little endian
|
|
Channels(2); // 2 channels
|
|
Frequency(freq);
|
|
setfragsize();
|
|
}
|
|
catch (...) {
|
|
throw;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void SoundOSS::Close(unsigned dir)
|
|
{
|
|
if (device_fd == -1)
|
|
return;
|
|
close(device_fd);
|
|
device_fd = -1;
|
|
}
|
|
|
|
void SoundOSS::getVersion()
|
|
{
|
|
version = 0;
|
|
#ifndef __FreeBSD__
|
|
if (ioctl(device_fd, OSS_GETVERSION, &version) == -1) {
|
|
version = -1;
|
|
throw SndException("OSS Version");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SoundOSS::getCapabilities()
|
|
{
|
|
capability_mask = 0;
|
|
if (ioctl(device_fd, SNDCTL_DSP_GETCAPS, &capability_mask) == -1) {
|
|
capability_mask = 0;
|
|
throw SndException("OSS capabilities");
|
|
}
|
|
}
|
|
|
|
void SoundOSS::getFormats()
|
|
{
|
|
format_mask = 0;
|
|
if (ioctl(device_fd, SNDCTL_DSP_GETFMTS, &format_mask) == -1) {
|
|
format_mask = 0;
|
|
throw SndException("OSS formats");
|
|
}
|
|
}
|
|
|
|
void SoundOSS::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 SoundOSS::Channels(int nuchannels)
|
|
{
|
|
channels = nuchannels;
|
|
if (ioctl(device_fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
|
|
device_fd = -1;
|
|
throw "Snd card channel request failed";
|
|
}
|
|
}
|
|
|
|
void SoundOSS::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 SoundOSS::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 SoundOSS::wait_till_finished()
|
|
{
|
|
if (ioctl(device_fd, SNDCTL_DSP_POST, (void*)1) == -1 )
|
|
return false;
|
|
if (ioctl(device_fd, SNDCTL_DSP_SYNC, (void*)0) == -1)
|
|
return false; /* format (or ioctl()) not supported by device */
|
|
return true; /* all sound has been played */
|
|
}
|
|
|
|
bool SoundOSS::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 */
|
|
}
|
|
|
|
size_t SoundOSS::Read(float *buffer, size_t buffersize)
|
|
{
|
|
short int *ibuff = (short int *)cbuff;
|
|
int numread;
|
|
|
|
numread = read(device_fd, cbuff, buffersize * 4);
|
|
if (numread == -1)
|
|
throw SndException(errno);
|
|
|
|
for (size_t i = 0; i < buffersize * 2; i++)
|
|
src_buffer[i] = ibuff[i] / MAXSC;
|
|
|
|
for (size_t i = 0; i < buffersize; i++)
|
|
buffer[i] = src_buffer[2*i +
|
|
progdefaults.ReverseRxAudio ? 1 : 0];
|
|
|
|
#if USE_SNDFILE
|
|
if (capture)
|
|
write_file(ofCapture, buffer, buffersize);
|
|
if (playback) {
|
|
read_file(ifPlayback, buffer, buffersize);
|
|
return buffersize;
|
|
}
|
|
#endif
|
|
|
|
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 (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)
|
|
throw SndException(src_strerror(numread));
|
|
|
|
numread = rx_src_data->output_frames_gen;
|
|
|
|
for (int i = 0; i < numread; i++)
|
|
buffer[i] = snd_buffer[2*i +
|
|
progdefaults.sig_on_right_channel ? 1 : 0];
|
|
|
|
return numread;
|
|
|
|
}
|
|
|
|
size_t SoundOSS::Write(double *buf, size_t count)
|
|
{
|
|
int retval;
|
|
short int *wbuff;
|
|
unsigned char *p;
|
|
|
|
#if USE_SNDFILE
|
|
if (generate)
|
|
write_file(ofGenerate, buf, count);
|
|
#endif
|
|
|
|
if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
|
|
return count;
|
|
}
|
|
|
|
|
|
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 (size_t i = 0; i < count; i++) {
|
|
wbuff[2*i] = wbuff[2*i+1] = (short int)(buf[i] * maxsc);
|
|
}
|
|
count *= sizeof(short int);
|
|
retval = write(device_fd, p, 2*count);
|
|
delete [] wbuff;
|
|
if (retval == -1)
|
|
throw SndException(errno);
|
|
}
|
|
else {
|
|
float *inbuf;
|
|
inbuf = new float[2*count];
|
|
size_t bufsize;
|
|
for (size_t 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;
|
|
|
|
retval = src_process(tx_src_state, tx_src_data);
|
|
delete [] inbuf;
|
|
if (retval != 0)
|
|
throw SndException(src_strerror(retval));
|
|
|
|
bufsize = tx_src_data->output_frames_gen;
|
|
wbuff = new short int[2*bufsize];
|
|
p = (unsigned char *)wbuff;
|
|
|
|
for (size_t i = 0; i < 2*bufsize; i++)
|
|
wbuff[i] = (short int)(src_buffer[i] * maxsc);
|
|
int num2write = bufsize * 2 * sizeof(short int);
|
|
|
|
retval = write(device_fd, p, num2write);
|
|
delete [] wbuff;
|
|
if (retval != num2write)
|
|
throw SndException(errno);
|
|
retval = count;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
size_t SoundOSS::Write_stereo(double *bufleft, double *bufright, size_t count)
|
|
{
|
|
int retval;
|
|
short int *wbuff;
|
|
unsigned char *p;
|
|
|
|
#if USE_SNDFILE
|
|
if (generate)
|
|
write_file(ofGenerate, bufleft, count);
|
|
#endif
|
|
|
|
if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
|
|
return count;
|
|
}
|
|
|
|
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 (size_t i = 0; i < count; i++) {
|
|
if (progdefaults.ReverseAudio) {
|
|
wbuff[2*i+1] = (short int)(bufleft[i] * maxsc);
|
|
wbuff[2*i] = (short int)(bufright[i] * maxsc);
|
|
} else {
|
|
wbuff[2*i] = (short int)(bufleft[i] * maxsc);
|
|
wbuff[2*i+1] = (short int)(bufright[i] * maxsc);
|
|
}
|
|
}
|
|
count *= sizeof(short int);
|
|
retval = write(device_fd, p, 2*count);
|
|
delete [] wbuff;
|
|
if (retval == -1)
|
|
throw SndException(errno);
|
|
}
|
|
else {
|
|
float *inbuf;
|
|
inbuf = new float[2*count];
|
|
size_t bufsize;
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (progdefaults.ReverseAudio) {
|
|
inbuf[2*i+1] = bufleft[i];
|
|
inbuf[2*i] = bufright[i];
|
|
} else {
|
|
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;
|
|
|
|
retval = src_process(tx_src_state, tx_src_data);
|
|
delete [] inbuf;
|
|
if (retval != 0)
|
|
throw SndException(src_strerror(retval));
|
|
|
|
bufsize = tx_src_data->output_frames_gen;
|
|
wbuff = new short int[2*bufsize];
|
|
p = (unsigned char *)wbuff;
|
|
|
|
for (size_t i = 0; i < 2*bufsize; i++)
|
|
wbuff[i] = (short int)(src_buffer[i] * maxsc);
|
|
|
|
int num2write = bufsize * 2 * sizeof(short int);
|
|
retval = write(device_fd, p, num2write);
|
|
delete [] wbuff;
|
|
if (retval != num2write)
|
|
throw SndException(errno);
|
|
retval = count;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
#endif // USE_OSS
|
|
|
|
|
|
#if USE_PORTAUDIO
|
|
|
|
bool SoundPort::pa_init = false;
|
|
std::vector<const PaDeviceInfo*> SoundPort::devs;
|
|
|
|
static ostringstream device_text[2];
|
|
static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
map<string, vector<double> > supported_rates[2];
|
|
|
|
void SoundPort::initialize(void)
|
|
{
|
|
if (pa_init)
|
|
return;
|
|
|
|
init_hostapi_ext();
|
|
|
|
int err;
|
|
if ((err = Pa_Initialize()) != paNoError) {
|
|
#if __WIN32__
|
|
LOG_PERROR(win_error_string(err).c_str());
|
|
#else
|
|
LOG_PERROR("Portaudio Initialize error");
|
|
#endif
|
|
throw SndPortException(err);
|
|
}
|
|
pa_init = true;
|
|
|
|
PaDeviceIndex ndev;
|
|
if ((ndev = Pa_GetDeviceCount()) < 0) {
|
|
LOG_PERROR("Portaudio device count error");
|
|
throw SndPortException(ndev);
|
|
}
|
|
if (ndev == 0) {
|
|
LOG_PERROR("Portaudio, no audio devices");
|
|
throw SndException(ENODEV, "No available audio devices");
|
|
}
|
|
|
|
devs.reserve(ndev);
|
|
for (PaDeviceIndex i = 0; i < ndev; i++)
|
|
devs.push_back(Pa_GetDeviceInfo(i));
|
|
}
|
|
void SoundPort::terminate(void)
|
|
{
|
|
if (!pa_init)
|
|
return;
|
|
static_cast<void>(Pa_Terminate());
|
|
pa_init = false;
|
|
devs.clear();
|
|
supported_rates[0].clear();
|
|
supported_rates[1].clear();
|
|
}
|
|
const std::vector<const PaDeviceInfo*>& SoundPort::devices(void)
|
|
{
|
|
return devs;
|
|
}
|
|
void SoundPort::devices_info(string& in, string& out)
|
|
{
|
|
guard_lock devices_lock(&device_mutex);
|
|
in = device_text[0].str();
|
|
out = device_text[1].str();
|
|
}
|
|
|
|
const vector<double>& SoundPort::get_supported_rates(const string& name, unsigned dir)
|
|
{
|
|
return supported_rates[dir][name];
|
|
}
|
|
|
|
|
|
SoundPort::SoundPort(const char *in_dev, const char *out_dev) : req_sample_rate(0)
|
|
{
|
|
sd[0].device = in_dev;
|
|
sd[0].params.channelCount = 2; // init_stream can change this to 0 or 1
|
|
sd[0].stream = 0;
|
|
sd[0].frames_per_buffer = paFramesPerBufferUnspecified;
|
|
sd[0].dev_sample_rate = 0;
|
|
sd[0].state = spa_continue;
|
|
sd[0].rb = 0;
|
|
sd[0].advance = 0;
|
|
|
|
sd[1].device = out_dev;
|
|
sd[1].params.channelCount = 2;
|
|
sd[1].stream = 0;
|
|
sd[1].frames_per_buffer = paFramesPerBufferUnspecified;
|
|
sd[1].dev_sample_rate = 0;
|
|
sd[1].state = spa_continue;
|
|
sd[1].rb = 0;
|
|
sd[1].advance = 0;
|
|
|
|
sem_t** sems[] = { &sd[0].rwsem, &sd[1].rwsem };
|
|
#if USE_NAMED_SEMAPHORES
|
|
char sname[32];
|
|
#endif
|
|
for (size_t i = 0; i < sizeof(sems)/sizeof(*sems); i++) {
|
|
#if USE_NAMED_SEMAPHORES
|
|
snprintf(sname, sizeof(sname), "%" PRIuSZ "-%u-%s", i, getpid(), PACKAGE_TARNAME);
|
|
if ((*sems[i] = sem_open(sname, O_CREAT | O_EXCL, 0600, 0)) == (sem_t*)SEM_FAILED) {
|
|
pa_perror(errno, sname);
|
|
throw SndException(errno);
|
|
}
|
|
# if HAVE_SEM_UNLINK
|
|
if (sem_unlink(sname) == -1) {
|
|
pa_perror(errno, sname);
|
|
throw SndException(errno);
|
|
}
|
|
# endif
|
|
#else
|
|
*sems[i] = new sem_t;
|
|
if (sem_init(*sems[i], 0, 0) == -1) {
|
|
#if __WIN32__
|
|
int err = GetLastError();
|
|
LOG_PERROR(win_error_string(err).c_str());
|
|
#endif
|
|
pa_perror(errno, "sem_init error");
|
|
throw SndException(errno);
|
|
}
|
|
#endif // USE_NAMED_SEMAPHORES
|
|
}
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
sd[i].cmutex = new pthread_mutex_t;
|
|
pthread_mutex_init(sd[i].cmutex, NULL);
|
|
sd[i].ccond = new pthread_cond_t;
|
|
pthread_cond_init(sd[i].ccond, NULL);
|
|
}
|
|
|
|
tx_src_data = new SRC_DATA;
|
|
src_buffer = new float[sd[1].params.channelCount * SND_BUF_LEN];
|
|
fbuf = new float[2 * SND_BUF_LEN];
|
|
|
|
memset(src_buffer, 0, sd[1].params.channelCount * SND_BUF_LEN * sizeof(*src_buffer));
|
|
memset(fbuf, 0, 2 * SND_BUF_LEN * sizeof(*fbuf));
|
|
}
|
|
|
|
SoundPort::~SoundPort()
|
|
{
|
|
Close();
|
|
|
|
sem_t* sems[] = { sd[0].rwsem, sd[1].rwsem };
|
|
for (size_t i = 0; i < sizeof(sems)/sizeof(*sems); i++) {
|
|
#if USE_NAMED_SEMAPHORES
|
|
if (sem_close(sems[i]) == -1)
|
|
LOG_PERROR("sem_close");
|
|
#else
|
|
if (sem_destroy(sems[i]) == -1)
|
|
LOG_PERROR("sem_destroy");
|
|
delete sems[i];
|
|
#endif
|
|
}
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
if (pthread_mutex_destroy(sd[i].cmutex) == -1) {
|
|
pa_perror(errno, "pthread mutex destroy");
|
|
throw SndException(errno);
|
|
}
|
|
delete sd[i].cmutex;
|
|
if (pthread_cond_destroy(sd[i].ccond) == -1) {
|
|
pa_perror(errno, "pthread cond destroy");
|
|
throw SndException(errno);
|
|
}
|
|
delete sd[i].ccond;
|
|
}
|
|
|
|
delete sd[0].rb;
|
|
delete sd[1].rb;
|
|
|
|
if (rx_src_state)
|
|
src_delete(rx_src_state);
|
|
if (tx_src_state)
|
|
src_delete(tx_src_state);
|
|
|
|
delete tx_src_data;
|
|
delete [] src_buffer;
|
|
delete [] fbuf;
|
|
}
|
|
|
|
int SoundPort::Open(int mode, int freq)
|
|
{
|
|
int old_sample_rate = (int)req_sample_rate;
|
|
req_sample_rate = sample_frequency = freq;
|
|
|
|
// do we need to (re)initialise the streams?
|
|
int ret = 0;
|
|
int sr[2] = { progdefaults.in_sample_rate, progdefaults.out_sample_rate };
|
|
|
|
// initialize stream if it is a JACK device, regardless of mode
|
|
device_iterator idev;
|
|
int device_type = 0;
|
|
if (mode == O_WRONLY && (idev = name_to_device(sd[0].device, 0)) != devs.end() &&
|
|
(device_type = Pa_GetHostApiInfo((*idev)->hostApi)->type) == paJACK)
|
|
mode = O_RDWR;
|
|
if (mode == O_RDONLY && (idev = name_to_device(sd[1].device, 1)) != devs.end() &&
|
|
(device_type = Pa_GetHostApiInfo((*idev)->hostApi)->type) == paJACK)
|
|
mode = O_RDWR;
|
|
|
|
size_t start = (mode == O_RDONLY || mode == O_RDWR) ? 0 : 1,
|
|
end = (mode == O_WRONLY || mode == O_RDWR) ? 1 : 0;
|
|
for (size_t i = start; i <= end; i++) {
|
|
if ( !(stream_active(i) && (Pa_GetHostApiInfo((*sd[i].idev)->hostApi)->type == paJACK ||
|
|
old_sample_rate == freq ||
|
|
sr[i] != SAMPLE_RATE_AUTO)) ) {
|
|
Close(i);
|
|
init_stream(i);
|
|
src_data_reset(i);
|
|
|
|
// reset the semaphore
|
|
while (sem_trywait(sd[i].rwsem) == 0);
|
|
if (errno && errno != EAGAIN) {
|
|
pa_perror(errno, "open");
|
|
throw SndException(errno);
|
|
}
|
|
start_stream(i);
|
|
|
|
ret = 1;
|
|
}
|
|
else {
|
|
pause_stream(i);
|
|
src_data_reset(i);
|
|
sd[i].state = spa_continue;
|
|
}
|
|
}
|
|
|
|
static char pa_open_str[500];
|
|
snprintf(pa_open_str, sizeof(pa_open_str),
|
|
"\
|
|
Port Audio open mode = %s\n\
|
|
device type = %s\n\
|
|
device name = %s\n\
|
|
# input channels %d\n\
|
|
# output channels %d",
|
|
mode == O_WRONLY ? "Write" :
|
|
mode == O_RDONLY ? "Read" :
|
|
mode == O_RDWR ? "Read/Write" : "unknown",
|
|
device_type == 0 ? "paInDevelopment" :
|
|
device_type == 1 ? "paDirectSound" :
|
|
device_type == 2 ? "paMME" :
|
|
device_type == 3 ? "paASIO" :
|
|
device_type == 4 ? "paSoundManager" :
|
|
device_type == 5 ? "paCoreAudio" :
|
|
device_type == 7 ? "paOSS" :
|
|
device_type == 8 ? "paALSA" :
|
|
device_type == 9 ? "paAL" :
|
|
device_type == 10 ? "paBeOS" :
|
|
device_type == 11 ? "paWDMKS" :
|
|
device_type == 12 ? "paJACK" :
|
|
device_type == 13 ? "paWASAPI" :
|
|
device_type == 14 ? "paAudioScienceHPI" : "unknown",
|
|
mode == O_WRONLY ? sd[1].device.c_str() :
|
|
mode == O_RDONLY ? sd[0].device.c_str() : "unknown",
|
|
sd[0].params.channelCount,
|
|
sd[1].params.channelCount );
|
|
LOG_VERBOSE( "%s", pa_open_str);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SoundPort::pause_stream(unsigned dir)
|
|
{
|
|
if (sd[dir].stream == 0 || !stream_active(dir))
|
|
return;
|
|
|
|
pthread_mutex_lock(sd[dir].cmutex);
|
|
sd[dir].state = spa_pause;
|
|
if (pthread_cond_timedwait_rel(sd[dir].ccond, sd[dir].cmutex, 5.0) == -1 && errno == ETIMEDOUT)
|
|
LOG_ERROR("stream %u wedged", dir);
|
|
pthread_mutex_unlock(sd[dir].cmutex);
|
|
}
|
|
|
|
void SoundPort::Close(unsigned dir)
|
|
{
|
|
unsigned start, end;
|
|
if (dir == UINT_MAX) {
|
|
start = 0;
|
|
end = 1;
|
|
}
|
|
else
|
|
start = end = dir;
|
|
|
|
for (unsigned i = start; i <= end; i++) {
|
|
if (!stream_active(i))
|
|
continue;
|
|
|
|
pthread_mutex_lock(sd[i].cmutex);
|
|
sd[i].state = spa_complete;
|
|
// first wait for buffers to be drained and for the
|
|
// stop callback to signal us that the stream has
|
|
// been stopped
|
|
if (pthread_cond_timedwait_rel(sd[i].ccond, sd[i].cmutex, 5.0) == -1 &&
|
|
errno == ETIMEDOUT)
|
|
LOG_ERROR("stream %u wedged", i);
|
|
pthread_mutex_unlock(sd[i].cmutex);
|
|
sd[i].state = spa_continue;
|
|
|
|
int err;
|
|
if ((err = Pa_CloseStream(sd[i].stream)) != paNoError)
|
|
pa_perror(err, "Pa_CloseStream");
|
|
|
|
sd[i].stream = 0;
|
|
}
|
|
}
|
|
|
|
void SoundPort::Abort(unsigned dir)
|
|
{
|
|
unsigned start, end;
|
|
if (dir == UINT_MAX) {
|
|
start = 0;
|
|
end = 1;
|
|
}
|
|
else
|
|
start = end = dir;
|
|
|
|
int err;
|
|
for (unsigned i = start; i <= end; i++) {
|
|
if (!stream_active(i))
|
|
continue;
|
|
if ((err = Pa_AbortStream(sd[i].stream)) != paNoError)
|
|
#if __WIN32__
|
|
LOG_PERROR(win_error_string(err).c_str());
|
|
#endif
|
|
pa_perror(err, "Pa_AbortStream");
|
|
sd[i].stream = 0;
|
|
}
|
|
}
|
|
|
|
|
|
#define WAIT_FOR_COND(cond, s, t) \
|
|
do { \
|
|
while (!(cond)) { \
|
|
if (sem_timedwait_rel(s, t) == -1) { \
|
|
if (errno == ETIMEDOUT) { \
|
|
timeout = true; \
|
|
break; \
|
|
} else if (errno == EINTR) { \
|
|
continue; \
|
|
} \
|
|
LOG_PERROR("sem_timedwait"); \
|
|
throw SndException(errno); \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
size_t SoundPort::Read(float *buf, size_t count)
|
|
{
|
|
#if USE_SNDFILE
|
|
if (playback) {
|
|
read_file(ifPlayback, buf, count);
|
|
if (!capture) {
|
|
if (!bHighSpeed)
|
|
MilliSleep((long)ceil((1e3 * count) / req_sample_rate));
|
|
return count;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (rxppm != progdefaults.RX_corr)
|
|
rxppm = progdefaults.RX_corr;
|
|
|
|
sd[0].src_ratio = req_sample_rate / (sd[0].dev_sample_rate * (1.0 + rxppm / 1e6));
|
|
src_set_ratio(rx_src_state, sd[0].src_ratio);
|
|
|
|
size_t maxframes = (size_t)floor(sd[0].rb->length() * sd[0].src_ratio / sd[0].params.channelCount);
|
|
|
|
if (unlikely(count > maxframes)) {
|
|
size_t n = 0;
|
|
#define PA_TIMEOUT_TRIES 10
|
|
int pa_timeout = PA_TIMEOUT_TRIES;
|
|
// possible to lock up in this while block if the Read(...) fails
|
|
while (count > maxframes) {
|
|
n += Read(buf, maxframes);
|
|
buf += maxframes * sd[0].params.channelCount;
|
|
count -= n;//maxframes;
|
|
pa_timeout--;
|
|
if (pa_timeout == 0) {
|
|
#if __WIN32__
|
|
int err = GetLastError();
|
|
LOG_PERROR(win_error_string(err).c_str());
|
|
#endif
|
|
pa_perror(1, "Portaudio read error #1");
|
|
throw SndException("Portaudio read error 1");
|
|
}
|
|
}
|
|
if (count > 0)
|
|
n += Read(buf, count);
|
|
return n;
|
|
}
|
|
|
|
float* rbuf = fbuf;
|
|
if (req_sample_rate != sd[0].dev_sample_rate || rxppm != 0) {
|
|
long r;
|
|
size_t n = 0;
|
|
sd[0].blocksize = SCBLOCKSIZE;
|
|
while (n < count) {
|
|
if ((r = src_callback_read(rx_src_state, sd[0].src_ratio,
|
|
count - n, rbuf + n * sd[0].params.channelCount)) == 0) {
|
|
pa_perror(2, "Portaudio read error #2");
|
|
throw SndException("Portaudio read error 2");
|
|
}
|
|
n += r;
|
|
}
|
|
}
|
|
else {
|
|
bool timeout = false;
|
|
WAIT_FOR_COND( (sd[0].rb->read_space() >= count * sd[0].params.channelCount / sd[0].src_ratio), sd[0].rwsem,
|
|
(MAX(1.0, 2 * count * sd[0].params.channelCount / sd->dev_sample_rate)) );
|
|
if (timeout) {
|
|
pa_perror(3, "Portaudio read error #3");
|
|
throw SndException("Portaudio read error 3");
|
|
}
|
|
ringbuffer<float>::vector_type vec[2];
|
|
sd[0].rb->get_rv(vec);
|
|
if (vec[0].len >= count * sd[0].params.channelCount) {
|
|
rbuf = vec[0].buf;
|
|
sd[0].advance = vec[0].len;
|
|
}
|
|
else
|
|
sd[0].rb->read(rbuf, count * sd[0].params.channelCount);
|
|
}
|
|
if (sd[0].advance) {
|
|
sd[0].rb->read_advance(sd[0].advance);
|
|
sd[0].advance = 0;
|
|
}
|
|
|
|
// transfer active input channel; left == 0, right == 1
|
|
size_t n;
|
|
for (size_t i = 0; i < count; i++) {
|
|
n = sd[0].params.channelCount * i;
|
|
if (sd[0].params.channelCount == 2)
|
|
n += progdefaults.ReverseRxAudio;
|
|
buf[i] = rbuf[n];
|
|
}
|
|
|
|
#if USE_SNDFILE
|
|
if (capture)
|
|
write_file(ofCapture, buf, count);
|
|
#endif
|
|
|
|
return count;
|
|
}
|
|
|
|
size_t SoundPort::Write(double *buf, size_t count)
|
|
{
|
|
#if USE_SNDFILE
|
|
if (generate)
|
|
write_file(ofGenerate, buf, count);
|
|
#endif
|
|
|
|
if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
|
|
return count;
|
|
}
|
|
|
|
// copy input to both channels if right channel enabled
|
|
for (size_t i = 0; i < count; i++)
|
|
if (progdefaults.sig_on_right_channel)
|
|
fbuf[sd[1].params.channelCount * i] = fbuf[sd[1].params.channelCount * i + 1] = buf[i];
|
|
else if (progdefaults.ReverseAudio) {
|
|
fbuf[sd[1].params.channelCount * i + 1] = buf[i];
|
|
fbuf[sd[1].params.channelCount * i] = 0;
|
|
} else {
|
|
fbuf[sd[1].params.channelCount * i] = buf[i];
|
|
fbuf[sd[1].params.channelCount * i + 1] = 0;
|
|
}
|
|
|
|
return resample_write(fbuf, count);
|
|
}
|
|
|
|
size_t SoundPort::Write_stereo(double *bufleft, double *bufright, size_t count)
|
|
{
|
|
if (sd[1].params.channelCount != 2)
|
|
return Write(bufleft, count);
|
|
|
|
#if USE_SNDFILE
|
|
if (generate)
|
|
write_file(ofCapture, bufleft, count);
|
|
#endif
|
|
|
|
if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
|
|
return count;
|
|
}
|
|
|
|
// interleave into fbuf
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (progdefaults.ReverseAudio) {
|
|
fbuf[sd[1].params.channelCount * i + 1] = bufleft[i];
|
|
fbuf[sd[1].params.channelCount * i] = bufright[i];
|
|
} else {
|
|
fbuf[sd[1].params.channelCount * i] = bufleft[i];
|
|
fbuf[sd[1].params.channelCount * i + 1] = bufright[i];
|
|
}
|
|
}
|
|
|
|
return resample_write(fbuf, count);
|
|
}
|
|
|
|
|
|
size_t SoundPort::resample_write(float* buf, size_t count)
|
|
{
|
|
size_t maxframes = (size_t)floor((sd[1].rb->length() / sd[1].params.channelCount) / tx_src_data->src_ratio);
|
|
maxframes /= 2; // don't fill the buffer
|
|
|
|
if (unlikely(count > maxframes)) {
|
|
size_t n = 0;
|
|
#define PA_TIMEOUT_TRIES 10
|
|
int pa_timeout = PA_TIMEOUT_TRIES;
|
|
// possible to lock up in this while block if the resample_write(...) fails
|
|
while (count > maxframes) {
|
|
n += resample_write(buf, maxframes);
|
|
buf += sd[1].params.channelCount * maxframes;
|
|
count -= maxframes;
|
|
pa_timeout--;
|
|
if (pa_timeout == 0) {
|
|
pa_perror(1, "Portaudio write error #1");
|
|
throw SndException("Portaudio write error 1");
|
|
}
|
|
}
|
|
if (count > 0)
|
|
n += resample_write(buf, count);
|
|
return n;
|
|
}
|
|
|
|
assert(count * sd[1].params.channelCount * tx_src_data->src_ratio <= sd[1].rb->length());
|
|
|
|
ringbuffer<float>::vector_type vec[2];
|
|
sd[1].rb->get_wv(vec);
|
|
float* wbuf = buf;
|
|
if (req_sample_rate != sd[1].dev_sample_rate || progdefaults.TX_corr != 0) {
|
|
if (vec[0].len >= sd[1].params.channelCount * (size_t)ceil(count * tx_src_data->src_ratio))
|
|
wbuf = vec[0].buf; // direct write in the rb
|
|
else
|
|
wbuf = src_buffer;
|
|
|
|
if (txppm != progdefaults.TX_corr)
|
|
txppm = progdefaults.TX_corr;
|
|
|
|
tx_src_data->src_ratio = sd[1].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 = wbuf;
|
|
tx_src_data->output_frames = (wbuf == vec[0].buf ? vec[0].len : SND_BUF_LEN);
|
|
tx_src_data->end_of_input = 0;
|
|
int r;
|
|
if ((r = src_process(tx_src_state, tx_src_data)) != 0) {
|
|
pa_perror(2, "Portaudio write error #2");
|
|
throw SndException("Portaudio write error 2");
|
|
}
|
|
if (tx_src_data->output_frames_gen == 0) // input was too small
|
|
return count;
|
|
|
|
count = tx_src_data->output_frames_gen;
|
|
if (wbuf == vec[0].buf) { // advance write pointer and return
|
|
sd[1].rb->write_advance(sd[1].params.channelCount * count);
|
|
sem_trywait(sd[1].rwsem);
|
|
return count;
|
|
}
|
|
}
|
|
|
|
// if we didn't do a direct resample into the rb, or didn't resample at all,
|
|
// we must now copy buf into the ringbuffer, possibly waiting for space first
|
|
bool timeout = false;
|
|
WAIT_FOR_COND( (sd[1].rb->write_space() >= sd[1].params.channelCount * count), sd[1].rwsem,
|
|
(MAX(1.0, 2 * sd[1].params.channelCount * count / sd[1].dev_sample_rate)) );
|
|
if (timeout) {
|
|
pa_perror(3, "Portaudio write error #3");
|
|
throw SndException("Portaudio write error 3");
|
|
}
|
|
sd[1].rb->write(wbuf, sd[1].params.channelCount * count);
|
|
|
|
return count;
|
|
}
|
|
|
|
void SoundPort::flush(unsigned dir)
|
|
{
|
|
unsigned start, end;
|
|
if (dir == UINT_MAX) {
|
|
start = 0;
|
|
end = 1;
|
|
}
|
|
else
|
|
start = end = dir;
|
|
|
|
for (unsigned i = start; i <= end; i++) {
|
|
if (!stream_active(i))
|
|
continue;
|
|
|
|
pthread_mutex_lock(sd[i].cmutex);
|
|
sd[i].state = spa_drain;
|
|
if (pthread_cond_timedwait_rel(sd[i].ccond, sd[i].cmutex, 5.0) == -1
|
|
&& errno == ETIMEDOUT)
|
|
LOG_ERROR("stream %u wedged", i);
|
|
pthread_mutex_unlock(sd[i].cmutex);
|
|
sd[i].state = spa_continue;
|
|
}
|
|
}
|
|
|
|
void SoundPort::src_data_reset(unsigned dir)
|
|
{
|
|
size_t rbsize;
|
|
|
|
int err;
|
|
if (dir == 0) {
|
|
if (rx_src_state)
|
|
src_delete(rx_src_state);
|
|
rx_src_state = src_callback_new(src_read_cb, progdefaults.sample_converter,
|
|
sd[0].params.channelCount, &err, &sd[0]);
|
|
if (!rx_src_state) {
|
|
pa_perror(err, src_strerror(err));
|
|
throw SndException(src_strerror(err));
|
|
}
|
|
sd[0].src_ratio = req_sample_rate / (sd[0].dev_sample_rate * (1.0 + rxppm / 1e6));
|
|
}
|
|
else if (dir == 1) {
|
|
if (tx_src_state)
|
|
src_delete(tx_src_state);
|
|
tx_src_state = src_new(progdefaults.sample_converter, sd[1].params.channelCount, &err);
|
|
if (!tx_src_state) {
|
|
pa_perror(err, src_strerror(err));
|
|
throw SndException(src_strerror(err));
|
|
}
|
|
tx_src_data->src_ratio = sd[1].dev_sample_rate * (1.0 + txppm / 1e6) / req_sample_rate;
|
|
}
|
|
|
|
rbsize = 2 * MAX(ceil2(
|
|
(unsigned)(2 * sd[dir].params.channelCount * SCBLOCKSIZE *
|
|
MAX(req_sample_rate, sd[dir].dev_sample_rate) /
|
|
MIN(req_sample_rate, sd[dir].dev_sample_rate))),
|
|
8192);
|
|
LOG_VERBOSE("rbsize = %" PRIuSZ "", rbsize);
|
|
if (sd[dir].rb) delete sd[dir].rb;
|
|
sd[dir].rb = new ringbuffer<float>(rbsize);
|
|
}
|
|
|
|
long SoundPort::src_read_cb(void* arg, float** data)
|
|
{
|
|
struct stream_data* sd = reinterpret_cast<stream_data*>(arg);
|
|
|
|
// advance read pointer for previous read
|
|
if (sd->advance) {
|
|
sd->rb->read_advance(sd->advance);
|
|
sd->advance = 0;
|
|
}
|
|
|
|
// wait for data
|
|
bool timeout = false;
|
|
WAIT_FOR_COND( (sd->rb->read_space() >= (size_t)sd[0].params.channelCount * SCBLOCKSIZE), sd->rwsem,
|
|
(MAX(1.0, 2 * sd[0].params.channelCount * SCBLOCKSIZE / sd->dev_sample_rate)) );
|
|
if (timeout) {
|
|
*data = 0;
|
|
return 0;
|
|
}
|
|
|
|
ringbuffer<float>::vector_type vec[2];
|
|
sd->rb->get_rv(vec);
|
|
|
|
*data = vec[0].buf;
|
|
sd->advance = vec[0].len;
|
|
|
|
return vec[0].len / sd[0].params.channelCount;
|
|
}
|
|
|
|
SoundPort::device_iterator SoundPort::name_to_device(std::string &name, unsigned dir)
|
|
{
|
|
device_iterator i;
|
|
for (i = devs.begin(); i != devs.end(); ++i)
|
|
if (name == (*i)->name && (dir ? (*i)->maxOutputChannels : (*i)->maxInputChannels))
|
|
break;
|
|
return i;
|
|
}
|
|
|
|
void SoundPort::init_stream(unsigned dir)
|
|
{
|
|
const char* dir_str[2] = { "input", "output" };
|
|
PaDeviceIndex idx = paNoDevice;
|
|
|
|
LOG_DEBUG("looking for device \"%s\"", sd[dir].device.c_str());
|
|
|
|
if ((sd[dir].idev = name_to_device(sd[dir].device, dir)) != devs.end())
|
|
idx = sd[dir].idev - devs.begin();
|
|
if (idx == paNoDevice) { // no match
|
|
LOG_ERROR("Could not find device \"%s\", using default device", sd[dir].device.c_str());
|
|
PaDeviceIndex def = (dir == 0 ? Pa_GetDefaultInputDevice() : Pa_GetDefaultOutputDevice());
|
|
if (def == paNoDevice) {
|
|
pa_perror(paDeviceUnavailable, "Portaudio device unavailable");
|
|
throw SndPortException(paDeviceUnavailable);
|
|
}
|
|
sd[dir].idev = devs.begin() + def;
|
|
idx = def;
|
|
}
|
|
else if (sd[dir].idev == devs.end()) // if we only found a near-match point the idev iterator to it
|
|
sd[dir].idev = devs.begin() + idx;
|
|
|
|
const PaHostApiInfo* host_api = Pa_GetHostApiInfo((*sd[dir].idev)->hostApi);
|
|
int max_channels = dir ? (*sd[dir].idev)->maxOutputChannels :
|
|
(*sd[dir].idev)->maxInputChannels;
|
|
if ((host_api->type == paALSA || host_api->type == paOSS) && max_channels == 0) {
|
|
pa_perror(EBUSY, "Portaudio device busy");
|
|
throw SndException(EBUSY);
|
|
}
|
|
|
|
if (dir == 0) {
|
|
sd[0].params.device = idx;
|
|
sd[0].params.sampleFormat = paFloat32;
|
|
sd[0].params.suggestedLatency = (*sd[dir].idev)->defaultHighInputLatency;
|
|
sd[0].params.hostApiSpecificStreamInfo = NULL;
|
|
if (max_channels < 2)
|
|
sd[0].params.channelCount = max_channels;
|
|
if (max_channels == 0) {
|
|
pa_perror(EBUSY, "Portaudio device cannot open for read");
|
|
throw SndException(EBUSY);
|
|
}
|
|
}
|
|
else {
|
|
sd[1].params.device = idx;
|
|
sd[1].params.sampleFormat = paFloat32;
|
|
if (host_api->type == paMME)
|
|
sd[1].params.suggestedLatency = (*sd[dir].idev)->defaultLowOutputLatency;
|
|
else
|
|
sd[1].params.suggestedLatency = (*sd[dir].idev)->defaultHighOutputLatency;
|
|
sd[1].params.hostApiSpecificStreamInfo = NULL;
|
|
}
|
|
|
|
const vector<double>& rates = supported_rates[dir][(*sd[dir].idev)->name];
|
|
if (rates.size() <= 1)
|
|
probe_supported_rates(sd[dir].idev);
|
|
ostringstream ss;
|
|
if (rates.size() > 1)
|
|
copy(rates.begin() + 1, rates.end(), ostream_iterator<double>(ss, " "));
|
|
else
|
|
ss << "Unknown";
|
|
|
|
{
|
|
guard_lock devices_lock(&device_mutex);
|
|
|
|
device_text[dir].str("");
|
|
device_text[dir]
|
|
<< "index: " << idx
|
|
<< "\nname: " << (*sd[dir].idev)->name
|
|
<< "\nhost API: " << host_api->name
|
|
<< "\nmax input channels: " << (*sd[dir].idev)->maxInputChannels
|
|
<< "\nmax output channels: " << (*sd[dir].idev)->maxOutputChannels
|
|
<< "\ndefault sample rate: " << (*sd[dir].idev)->defaultSampleRate
|
|
<< "\nsupported sample rates: " << ss.str()
|
|
<< boolalpha
|
|
<< "\ninput only: " << ((*sd[dir].idev)->maxOutputChannels == 0)
|
|
<< "\noutput only: " << ((*sd[dir].idev)->maxInputChannels == 0)
|
|
<< "\nfull duplex: " << full_duplex_device(*sd[dir].idev)
|
|
<< "\nsystem default input: " << (idx == Pa_GetDefaultInputDevice())
|
|
<< "\nsystem default output: " << (idx == Pa_GetDefaultOutputDevice())
|
|
<< "\nhost API default input: " << (idx == host_api->defaultInputDevice)
|
|
<< "\nhost API default output: " << (idx == host_api->defaultOutputDevice)
|
|
<< "\ndefault low input latency: " << (*sd[dir].idev)->defaultLowInputLatency
|
|
<< "\ndefault high input latency: " << (*sd[dir].idev)->defaultHighInputLatency
|
|
<< "\ndefault low output latency: " << (*sd[dir].idev)->defaultLowOutputLatency
|
|
<< "\ndefault high output latency: " << (*sd[dir].idev)->defaultHighOutputLatency
|
|
<< "\n";
|
|
}
|
|
|
|
LOG_VERBOSE("using %s (%d ch) device \"%s\":\n%s", dir_str[dir], sd[dir].params.channelCount,
|
|
sd[dir].device.c_str(), device_text[dir].str().c_str());
|
|
|
|
sd[dir].dev_sample_rate = find_srate(dir);
|
|
if (sd[dir].dev_sample_rate != req_sample_rate)
|
|
LOG_DEBUG("%s: resampling %f <=> %f", dir_str[dir],
|
|
sd[dir].dev_sample_rate, req_sample_rate);
|
|
|
|
if (progdefaults.PortFramesPerBuffer > 0) {
|
|
sd[dir].frames_per_buffer = progdefaults.PortFramesPerBuffer;
|
|
LOG_DEBUG("%s: frames_per_buffer=%u", dir_str[dir], sd[dir].frames_per_buffer);
|
|
}
|
|
}
|
|
|
|
void SoundPort::start_stream(unsigned dir)
|
|
{
|
|
int err;
|
|
|
|
PaStreamParameters* sp[2];
|
|
sp[dir] = &sd[dir].params;
|
|
sp[!dir] = NULL;
|
|
|
|
err = Pa_OpenStream(&sd[dir].stream, sp[0], sp[1],
|
|
sd[dir].dev_sample_rate, sd[dir].frames_per_buffer,
|
|
paNoFlag,
|
|
stream_process, &sd[dir]);
|
|
if (err != paNoError) {
|
|
pa_perror(err, "Portaudio open stream error");
|
|
throw SndPortException(err);
|
|
}
|
|
|
|
if ((err = Pa_SetStreamFinishedCallback(sd[dir].stream, stream_stopped)) != paNoError)
|
|
throw SndPortException(err);
|
|
|
|
if ((err = Pa_StartStream(sd[dir].stream)) != paNoError) {
|
|
pa_perror(err, "Portaudio stream start stream error");
|
|
Close();
|
|
throw SndPortException(err);
|
|
}
|
|
}
|
|
|
|
|
|
int SoundPort::stream_process(
|
|
const void* in, void* out, unsigned long nframes,
|
|
const PaStreamCallbackTimeInfo *time_info,
|
|
PaStreamCallbackFlags flags, void* data)
|
|
{
|
|
struct stream_data* sd = reinterpret_cast<struct stream_data*>(data);
|
|
|
|
#ifndef NDEBUG
|
|
struct {
|
|
PaStreamCallbackFlags f;
|
|
const char* s;
|
|
} fa[] = { { paInputUnderflow, "Input underflow" },
|
|
{ paInputOverflow, "Input overflow" },
|
|
{ paOutputUnderflow, "Output underflow" },
|
|
{ paOutputOverflow, "Output overflow" }
|
|
};
|
|
for (size_t i = 0; i < sizeof(fa)/sizeof(*fa); i++)
|
|
if (flags & fa[i].f)
|
|
LOG_DEBUG("%s", fa[i].s);
|
|
#endif
|
|
|
|
if (unlikely(sd->state == spa_abort || sd->state == spa_complete)) // finished
|
|
return sd->state;
|
|
|
|
if (in) {
|
|
switch (sd->state) {
|
|
case spa_continue: // write into the rb, post rwsem if we wrote anything
|
|
if (sd->rb->write(reinterpret_cast<const float*>(in), sd->params.channelCount * nframes))
|
|
sem_post(sd->rwsem);
|
|
break;
|
|
case spa_drain: case spa_pause: // signal the cv
|
|
pthread_mutex_lock(sd->cmutex);
|
|
pthread_cond_signal(sd->ccond);
|
|
pthread_mutex_unlock(sd->cmutex);
|
|
}
|
|
}
|
|
else if (out) {
|
|
float* outf = reinterpret_cast<float*>(out);
|
|
// if we are paused just pretend that the rb was empty
|
|
size_t nread = (sd->state == spa_pause) ? 0 : sd->rb->read(outf, sd->params.channelCount * nframes);
|
|
memset(outf + nread, 0, (sd->params.channelCount * nframes - nread) * sizeof(float)); // fill rest with 0
|
|
|
|
switch (sd->state) {
|
|
case spa_continue: // post rwsem if we read anything
|
|
if (nread > 0)
|
|
sem_post(sd->rwsem);
|
|
break;
|
|
case spa_drain: // signal the cv when we have emptied the buffer
|
|
if (nread > 0)
|
|
break;
|
|
// else fall through
|
|
case spa_pause:
|
|
pthread_mutex_lock(sd->cmutex);
|
|
pthread_cond_signal(sd->ccond);
|
|
pthread_mutex_unlock(sd->cmutex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return paContinue;
|
|
}
|
|
|
|
void SoundPort::stream_stopped(void* data)
|
|
{
|
|
struct stream_data* sd = reinterpret_cast<struct stream_data*>(data);
|
|
|
|
if (sd->rb)
|
|
sd->rb->reset();
|
|
pthread_mutex_lock(sd->cmutex);
|
|
pthread_cond_signal(sd->ccond);
|
|
pthread_mutex_unlock(sd->cmutex);
|
|
}
|
|
|
|
|
|
bool SoundPort::stream_active(unsigned dir)
|
|
{
|
|
if (!sd[dir].stream)
|
|
return false;
|
|
|
|
int err;
|
|
if ((err = Pa_IsStreamActive(sd[dir].stream)) < 0) {
|
|
pa_perror(err, "Portaudio stream active error");
|
|
throw SndPortException(err);
|
|
}
|
|
return err == 1;
|
|
}
|
|
|
|
bool SoundPort::full_duplex_device(const PaDeviceInfo* dev)
|
|
{
|
|
return dev->maxInputChannels > 0 && dev->maxOutputChannels > 0;
|
|
}
|
|
|
|
bool SoundPort::must_close(int dir)
|
|
{
|
|
return Pa_GetHostApiInfo((*sd[dir].idev)->hostApi)->type != paJACK;
|
|
}
|
|
|
|
double SoundPort::find_srate(unsigned dir)
|
|
{
|
|
int sr = (dir == 0 ? progdefaults.in_sample_rate : progdefaults.out_sample_rate);
|
|
switch (sr) {
|
|
case SAMPLE_RATE_UNSET: case SAMPLE_RATE_AUTO:
|
|
break;
|
|
case SAMPLE_RATE_NATIVE:
|
|
return (*sd[dir].idev)->defaultSampleRate;
|
|
default:
|
|
return sr;
|
|
}
|
|
|
|
const vector<double>& rates = supported_rates[dir][(*sd[dir].idev)->name];
|
|
for (vector<double>::const_iterator i = rates.begin(); i != rates.end(); i++)
|
|
if (req_sample_rate == *i || (*sd[dir].idev)->defaultSampleRate == *i)
|
|
return *i;
|
|
|
|
pa_perror(0, "Portaudio - no supported sample rate found");
|
|
throw SndException("No supported sample rate found");
|
|
}
|
|
|
|
void SoundPort::probe_supported_rates(const device_iterator& idev)
|
|
{
|
|
PaStreamParameters params[2];
|
|
params[0].device = params[1].device = idev - devs.begin();
|
|
params[0].channelCount = 2;
|
|
params[1].channelCount = 2;
|
|
params[0].sampleFormat = params[1].sampleFormat = paFloat32;
|
|
params[0].suggestedLatency = (*idev)->defaultHighInputLatency;
|
|
params[1].suggestedLatency = (*idev)->defaultHighOutputLatency;
|
|
params[0].hostApiSpecificStreamInfo = params[1].hostApiSpecificStreamInfo = NULL;
|
|
|
|
supported_rates[0][(*idev)->name].clear();
|
|
supported_rates[1][(*idev)->name].clear();
|
|
supported_rates[0][(*idev)->name].push_back((*idev)->defaultSampleRate);
|
|
supported_rates[1][(*idev)->name].push_back((*idev)->defaultSampleRate);
|
|
extern double std_sample_rates[];
|
|
for (const double* r = std_sample_rates; *r > 0.0; r++) {
|
|
if (Pa_IsFormatSupported(¶ms[0], NULL, *r) == paFormatIsSupported)
|
|
supported_rates[0][(*idev)->name].push_back(*r);
|
|
if (Pa_IsFormatSupported(NULL, ¶ms[1], *r) == paFormatIsSupported)
|
|
supported_rates[1][(*idev)->name].push_back(*r);
|
|
}
|
|
}
|
|
|
|
void SoundPort::pa_perror(int err, const char* str)
|
|
{
|
|
if (str)
|
|
LOG_ERROR("%s: %s", str, Pa_GetErrorText(err));
|
|
|
|
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...
|
|
LOG_ERROR("Host API error info not available");
|
|
if ( ((sd[0].stream && Pa_GetHostApiInfo((*sd[0].idev)->hostApi)->type == paOSS) ||
|
|
(sd[1].stream && Pa_GetHostApiInfo((*sd[1].idev)->hostApi)->type == paOSS)) &&
|
|
errno )
|
|
LOG_ERROR("Possible OSS error %d: %s", errno, strerror(errno));
|
|
}
|
|
else
|
|
LOG_ERROR("%s error %ld: %s", Pa_GetHostApiInfo(i)->name,
|
|
hosterr->errorCode, hosterr->errorText);
|
|
}
|
|
}
|
|
|
|
void SoundPort::init_hostapi_ext(void)
|
|
{
|
|
#if HAVE_DLOPEN && !defined(__WOE32__)
|
|
void* handle = dlopen(NULL, RTLD_LAZY);
|
|
if (!handle)
|
|
return;
|
|
|
|
PaError (*set_jack_client_name)(const char*);
|
|
const char* err = dlerror();
|
|
set_jack_client_name = (PaError (*)(const char*))dlsym(handle, "PaJack_SetClientName");
|
|
if (!(err = dlerror()))
|
|
set_jack_client_name(main_window_title.c_str());
|
|
# ifndef NDEBUG
|
|
else
|
|
LOG_VERBOSE("dlsym(PaJack_SetClientName) error: %s", err);
|
|
# endif
|
|
#endif
|
|
}
|
|
|
|
#endif // USE_PORTAUDIO
|
|
|
|
|
|
#if USE_PULSEAUDIO
|
|
|
|
SoundPulse::SoundPulse(const char *dev)
|
|
{
|
|
sd[0].stream = 0;
|
|
sd[0].stream_params.channels = 2;
|
|
sd[0].dir = PA_STREAM_RECORD;
|
|
sd[0].stream_params.format = PA_SAMPLE_FLOAT32LE;
|
|
sd[0].buffer_attrs.maxlength = (uint32_t)-1;
|
|
sd[0].buffer_attrs.minreq = (uint32_t)-1;
|
|
sd[0].buffer_attrs.prebuf = (uint32_t)-1;
|
|
sd[0].buffer_attrs.fragsize = SCBLOCKSIZE * sizeof(float);
|
|
sd[0].buffer_attrs.tlength = (uint32_t)-1;
|
|
|
|
sd[1].stream = 0;
|
|
sd[1].dir = PA_STREAM_PLAYBACK;
|
|
sd[1].stream_params.format = PA_SAMPLE_FLOAT32LE;
|
|
sd[1].stream_params.channels = 2;
|
|
sd[1].buffer_attrs.maxlength = (uint32_t)-1;
|
|
sd[1].buffer_attrs.minreq = (uint32_t)-1;
|
|
sd[1].buffer_attrs.prebuf = (uint32_t)-1;
|
|
sd[1].buffer_attrs.fragsize = (uint32_t)-1;
|
|
sd[1].buffer_attrs.tlength = SCBLOCKSIZE * sizeof(float);
|
|
|
|
tx_src_data = new SRC_DATA;
|
|
|
|
snd_buffer = new float[sd[0].stream_params.channels * SND_BUF_LEN];
|
|
rbuf = new float[sd[0].stream_params.channels * SND_BUF_LEN];
|
|
|
|
src_buffer = new float[sd[1].stream_params.channels * SND_BUF_LEN];
|
|
fbuf = new float[sd[1].stream_params.channels * SND_BUF_LEN];
|
|
|
|
memset(snd_buffer, 0, sd[0].stream_params.channels * SND_BUF_LEN * sizeof(*snd_buffer));
|
|
memset(rbuf, 0, sd[0].stream_params.channels * SND_BUF_LEN * sizeof(*rbuf));
|
|
|
|
memset(src_buffer, 0, sd[1].stream_params.channels * SND_BUF_LEN * sizeof(*src_buffer));
|
|
memset(fbuf, 0, sd[1].stream_params.channels * SND_BUF_LEN * sizeof(*fbuf));
|
|
}
|
|
|
|
SoundPulse::~SoundPulse()
|
|
{
|
|
Close();
|
|
|
|
delete tx_src_data;
|
|
if (rx_src_state)
|
|
src_delete(rx_src_state);
|
|
if (tx_src_state)
|
|
src_delete(tx_src_state);
|
|
|
|
delete [] snd_buffer;
|
|
delete [] src_buffer;
|
|
delete [] fbuf;
|
|
delete [] rbuf;
|
|
}
|
|
|
|
int SoundPulse::Open(int mode, int freq)
|
|
{
|
|
const char* server = (progdefaults.PulseServer.length() ?
|
|
progdefaults.PulseServer.c_str() : NULL);
|
|
char sname[32];
|
|
int err;
|
|
|
|
sample_frequency = freq;
|
|
for (int i = 0; i < 2; i++) {
|
|
src_data_reset(1 << O_RDONLY | 1 << O_WRONLY);
|
|
|
|
if ((unsigned)freq != sd[i].stream_params.rate)
|
|
Close(i);
|
|
if (sd[i].stream)
|
|
continue;
|
|
|
|
sd[i].stream_params.rate = freq;
|
|
snprintf(sname, sizeof(sname), "%s (%u)", (i ? "playback" : "capture"), getpid());
|
|
setenv("PULSE_PROP_application.icon_name", PACKAGE_TARNAME, 1);
|
|
sd[i].stream = pa_simple_new(server, main_window_title.c_str(), sd[i].dir, NULL,
|
|
sname, &sd[i].stream_params, NULL,
|
|
&sd[i].buffer_attrs, &err);
|
|
if (!sd[i].stream)
|
|
throw SndPulseException(err);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SoundPulse::Close(unsigned dir)
|
|
{
|
|
if (dir == 1 || dir == UINT_MAX)
|
|
flush(1);
|
|
Abort(dir);
|
|
}
|
|
|
|
void SoundPulse::Abort(unsigned dir)
|
|
{
|
|
unsigned start, end;
|
|
if (dir == UINT_MAX) {
|
|
start = 0;
|
|
end = 1;
|
|
}
|
|
else
|
|
start = end = dir;
|
|
|
|
for (unsigned i = start; i <= end; i++) {
|
|
if (sd[i].stream) {
|
|
pa_simple_free(sd[i].stream);
|
|
sd[i].stream = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void SoundPulse::flush(unsigned dir)
|
|
{
|
|
int err = PA_OK;
|
|
if ((dir == 1 || dir == UINT_MAX) && sd[1].stream) {
|
|
// wait for audio to finish playing
|
|
// pa_simple_drain(sd[1].stream, &err);
|
|
MilliSleep(SCBLOCKSIZE * 1000 / sd[1].stream_params.rate);
|
|
pa_simple_flush(sd[1].stream, &err);
|
|
}
|
|
|
|
if ((dir == 0 || dir == UINT_MAX) && sd[0].stream) {
|
|
// We need to flush the captured audio that PA has been
|
|
// buffering for us while we were transmitting. We will use
|
|
// pa_simple_get_latency() which, contrary to the docs, also
|
|
// works for capture streams. It tells us how much earlier the
|
|
// data that would be returned by pa_simple_read() was actually
|
|
// captured, and we read and discard all that data.
|
|
pa_usec_t t = pa_simple_get_latency(sd[0].stream, &err);
|
|
if (t && err == PA_OK) {
|
|
size_t bytes = pa_usec_to_bytes(t, &sd[0].stream_params);
|
|
while (bytes > SND_BUF_LEN) {
|
|
pa_simple_read(sd[0].stream, snd_buffer, SND_BUF_LEN, &err);
|
|
if (err != PA_OK)
|
|
break;
|
|
bytes -= SND_BUF_LEN;
|
|
}
|
|
if (bytes)
|
|
pa_simple_read(sd[0].stream, snd_buffer, bytes, &err);
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t SoundPulse::Write(double* buf, size_t count)
|
|
{
|
|
#if USE_SNDFILE
|
|
if (generate)
|
|
write_file(ofGenerate, buf, count);
|
|
#endif
|
|
|
|
if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
|
|
return count;
|
|
}
|
|
|
|
// copy input to both channels
|
|
for (size_t i = 0; i < count; i++)
|
|
if (progdefaults.sig_on_right_channel)
|
|
fbuf[sd[1].stream_params.channels * i] = fbuf[sd[1].stream_params.channels * i + 1] = buf[i];
|
|
else if (progdefaults.ReverseAudio) {
|
|
fbuf[sd[1].stream_params.channels * i + 1] = buf[i];
|
|
fbuf[sd[1].stream_params.channels * i] = 0;
|
|
} else {
|
|
fbuf[sd[1].stream_params.channels * i] = buf[i];
|
|
fbuf[sd[1].stream_params.channels * i + 1] = 0;
|
|
}
|
|
|
|
return resample_write(fbuf, count);
|
|
}
|
|
|
|
size_t SoundPulse::Write_stereo(double* bufleft, double* bufright, size_t count)
|
|
{
|
|
if (sd[1].stream_params.channels != 2)
|
|
return Write(bufleft, count);
|
|
|
|
#if USE_SNDFILE
|
|
if (generate)
|
|
write_file(ofGenerate, bufleft, count);
|
|
#endif
|
|
|
|
if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
|
|
return count;
|
|
}
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (progdefaults.ReverseAudio) {
|
|
fbuf[sd[1].stream_params.channels * i + 1] = bufleft[i];
|
|
fbuf[sd[1].stream_params.channels * i] = bufright[i];
|
|
} else {
|
|
fbuf[sd[1].stream_params.channels * i] = bufleft[i];
|
|
fbuf[sd[1].stream_params.channels * i + 1] = bufright[i];
|
|
}
|
|
}
|
|
|
|
return resample_write(fbuf, count);
|
|
}
|
|
|
|
size_t SoundPulse::resample_write(float* buf, size_t count)
|
|
{
|
|
int err;
|
|
float *wbuf = buf;
|
|
|
|
if (progdefaults.TX_corr != 0) {
|
|
if (txppm != progdefaults.TX_corr) {
|
|
txppm = progdefaults.TX_corr;
|
|
tx_src_data->src_ratio = 1.0 + txppm / 1e6;
|
|
src_set_ratio(tx_src_state, tx_src_data->src_ratio);
|
|
}
|
|
tx_src_data->data_in = wbuf;
|
|
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 ((err = src_process(tx_src_state, tx_src_data)) != 0)
|
|
throw SndException(src_strerror(err));
|
|
if (tx_src_data->output_frames_gen == 0) // input was too small
|
|
return count;
|
|
|
|
wbuf = tx_src_data->data_out;
|
|
count = tx_src_data->output_frames_gen;
|
|
}
|
|
|
|
if (pa_simple_write(sd[1].stream, wbuf, count * sd[1].stream_params.channels * sizeof(float), &err) == -1)
|
|
throw SndPulseException(err);
|
|
|
|
return count;
|
|
}
|
|
|
|
long SoundPulse::src_read_cb(void* arg, float** data)
|
|
{
|
|
SoundPulse* p = reinterpret_cast<SoundPulse*>(arg);
|
|
|
|
int err;
|
|
int nread = 0;
|
|
if ((nread = pa_simple_read(p->sd[0].stream, p->snd_buffer,
|
|
p->sd[0].stream_params.channels * sizeof(float) * p->sd[0].blocksize, &err)) == -1) {
|
|
LOG_ERROR("%s", pa_strerror(err));
|
|
*data = 0;
|
|
return 0;
|
|
}
|
|
|
|
*data = p->snd_buffer;
|
|
return p->sd[0].blocksize;
|
|
}
|
|
|
|
size_t SoundPulse::Read(float *buf, size_t count)
|
|
{
|
|
#if USE_SNDFILE
|
|
if (playback) {
|
|
read_file(ifPlayback, buf, count);
|
|
if (!capture) {
|
|
flush(0);
|
|
if (!bHighSpeed)
|
|
MilliSleep((long)ceil((1e3 * count) / sample_frequency));
|
|
return count;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
size_t n = 0;
|
|
long r = 0;
|
|
|
|
if (progdefaults.RX_corr != 0) {
|
|
if (rxppm != progdefaults.RX_corr) {
|
|
rxppm = progdefaults.RX_corr;
|
|
sd[0].src_ratio = 1.0 / (1.0 + rxppm / 1e6);
|
|
src_set_ratio(rx_src_state, sd[0].src_ratio);
|
|
}
|
|
sd[0].blocksize = SCBLOCKSIZE;
|
|
while (n < count) {
|
|
if ((r = src_callback_read(rx_src_state, sd[0].src_ratio, count - n, rbuf + n)) == 0)
|
|
break;
|
|
n += r;
|
|
}
|
|
}
|
|
else {
|
|
int err;
|
|
if ((r = pa_simple_read(sd[0].stream, rbuf,
|
|
sd[0].stream_params.channels * sizeof(float) * count, &err)) == -1)
|
|
throw SndPulseException(err);
|
|
}
|
|
|
|
// transfer active input channel; left == 0, right == 1
|
|
size_t i = 0;
|
|
if (sd[0].stream_params.channels == 2) n = progdefaults.ReverseRxAudio;
|
|
else n = 0;
|
|
while (i < count) {
|
|
buf[i] = rbuf[n];
|
|
i++;
|
|
n += sd[0].stream_params.channels;
|
|
}
|
|
|
|
#if USE_SNDFILE
|
|
if (capture)
|
|
write_file(ofCapture, buf, count);
|
|
#endif
|
|
|
|
return count;
|
|
}
|
|
|
|
void SoundPulse::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_callback_new(src_read_cb, progdefaults.sample_converter,
|
|
sd[0].stream_params.channels, &err, this);
|
|
if (!rx_src_state)
|
|
throw SndException(src_strerror(err));
|
|
sd[0].src_ratio = 1.0 / (1.0 + rxppm / 1e6);
|
|
}
|
|
if (mode & 1 << O_WRONLY) {
|
|
if (tx_src_state)
|
|
src_delete(tx_src_state);
|
|
tx_src_state = src_new(progdefaults.sample_converter, sd[1].stream_params.channels, &err);
|
|
if (!tx_src_state)
|
|
throw SndException(src_strerror(err));
|
|
tx_src_data->src_ratio = 1.0 + txppm / 1e6;
|
|
}
|
|
}
|
|
|
|
#endif // USE_PULSEAUDIO
|
|
|
|
|
|
size_t SoundNull::Write(double* buf, size_t count)
|
|
{
|
|
#if USE_SNDFILE
|
|
if (generate)
|
|
write_file(ofGenerate, buf, count);
|
|
#endif
|
|
|
|
if (PERFORM_CPS_TEST || active_modem->XMLRPC_CPS_TEST) {
|
|
return count;
|
|
}
|
|
|
|
MilliSleep((long)ceil((1e3 * count) / sample_frequency));
|
|
|
|
return count;
|
|
}
|
|
|
|
size_t SoundNull::Write_stereo(double* bufleft, double* bufright, size_t count)
|
|
{
|
|
#if USE_SNDFILE
|
|
if (generate)
|
|
write_file(ofGenerate, bufleft, count);
|
|
#endif
|
|
|
|
MilliSleep((long)ceil((1e3 * count) / sample_frequency));
|
|
|
|
return count;
|
|
}
|
|
|
|
size_t SoundNull::Read(float *buf, size_t count)
|
|
{
|
|
#if USE_SNDFILE
|
|
if (playback) {
|
|
read_file(ifPlayback, buf, count);
|
|
}
|
|
else
|
|
#endif
|
|
memset(buf, 0, count * sizeof(*buf));
|
|
#if USE_SNDFILE
|
|
if (capture)
|
|
write_file(ofCapture, buf, count);
|
|
#endif
|
|
if (!bHighSpeed)
|
|
MilliSleep((long)ceil((1e3 * count) / sample_frequency));
|
|
|
|
return count;
|
|
|
|
}
|