You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
696 lines
18 KiB
696 lines
18 KiB
/*
|
|
* Copyright (C) 2006-2010,2015 by Jonathan Naylor G4KLX
|
|
* Copyright (C) 2014 by John Wiseman, G8BPQ
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "SoundCardReaderWriter.h"
|
|
#include "Logger.h"
|
|
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
|
|
#if !defined(_WIN32) && !(defined(__APPLE__) && defined(__MACH__))
|
|
|
|
std::vector<std::string> CSoundCardReaderWriter::m_readDevices;
|
|
std::vector<std::string> CSoundCardReaderWriter::m_writeDevices;
|
|
|
|
CSoundCardReaderWriter::CSoundCardReaderWriter(const std::string& readDevice, const std::string& writeDevice, unsigned int sampleRate, unsigned int blockSize) :
|
|
m_readDevice(readDevice),
|
|
m_writeDevice(writeDevice),
|
|
m_sampleRate(sampleRate),
|
|
m_blockSize(blockSize),
|
|
m_callback(nullptr),
|
|
m_id(-1),
|
|
m_reader(nullptr),
|
|
m_writer(nullptr)
|
|
{
|
|
assert(sampleRate > 0U);
|
|
assert(blockSize > 0U);
|
|
}
|
|
|
|
CSoundCardReaderWriter::~CSoundCardReaderWriter()
|
|
{
|
|
}
|
|
|
|
std::vector<std::string> CSoundCardReaderWriter::getReadDevices()
|
|
{
|
|
snd_ctl_t *handle = nullptr;
|
|
snd_pcm_t *pcm = nullptr;
|
|
char NameString[256];
|
|
|
|
std::vector<std::string> devices(m_readDevices);
|
|
|
|
snd_ctl_card_info_t* info;
|
|
snd_ctl_card_info_alloca(&info);
|
|
|
|
snd_pcm_info_t* pcminfo;
|
|
snd_pcm_info_alloca(&pcminfo);
|
|
|
|
snd_pcm_hw_params_t* pars;
|
|
snd_pcm_hw_params_alloca(&pars);
|
|
|
|
unsigned min, max;
|
|
int err;
|
|
snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE;
|
|
|
|
int card = -1;
|
|
while (::snd_card_next(&card) == 0 && card >= 0) {
|
|
char hwdev[80];
|
|
::snprintf(hwdev, sizeof(hwdev), "hw:%d", card);
|
|
|
|
if (::snd_ctl_open(&handle, hwdev, 0) < 0)
|
|
continue;
|
|
|
|
::snd_ctl_card_info(handle, info);
|
|
::snd_ctl_card_info_get_name(info);
|
|
|
|
int dev = -1;
|
|
while (::snd_ctl_pcm_next_device(handle, &dev) == 0 && dev >= 0) {
|
|
::snd_pcm_info_set_device(pcminfo, dev);
|
|
::snd_pcm_info_set_subdevice(pcminfo, 0);
|
|
::snd_pcm_info_set_stream(pcminfo, stream);
|
|
|
|
err = ::snd_ctl_pcm_info(handle, pcminfo);
|
|
if (err != -ENOENT) {
|
|
::snprintf(hwdev, sizeof(hwdev), "hw:%d,%d", card, dev);
|
|
|
|
if (::snd_pcm_open(&pcm, hwdev, stream, SND_PCM_NONBLOCK) < 0)
|
|
continue;
|
|
|
|
::snd_pcm_hw_params_any(pcm, pars);
|
|
::snd_pcm_hw_params_get_channels_min(pars, &min);
|
|
::snd_pcm_hw_params_get_channels_max(pars, &max);
|
|
|
|
::snd_pcm_hw_params_get_rate_min(pars, &min, nullptr);
|
|
::snd_pcm_hw_params_get_rate_max(pars, &max, nullptr);
|
|
|
|
::snprintf(NameString, sizeof(NameString), "hw:%d,%d %s(%s)",
|
|
card, dev,
|
|
::snd_pcm_info_get_name(pcminfo),
|
|
snd_ctl_card_info_get_name(info));
|
|
|
|
devices.push_back(std::string(NameString));
|
|
|
|
::snd_pcm_close(pcm);
|
|
pcm = nullptr;
|
|
}
|
|
}
|
|
|
|
::snd_ctl_close(handle);
|
|
}
|
|
|
|
return devices;
|
|
}
|
|
|
|
std::vector<std::string> CSoundCardReaderWriter::getWriteDevices()
|
|
{
|
|
snd_ctl_t *handle = nullptr;
|
|
snd_pcm_t *pcm = nullptr;
|
|
char NameString[256];
|
|
|
|
std::vector<std::string> devices(m_writeDevices);
|
|
|
|
snd_ctl_card_info_t* info;
|
|
snd_ctl_card_info_alloca(&info);
|
|
|
|
snd_pcm_info_t* pcminfo;
|
|
snd_pcm_info_alloca(&pcminfo);
|
|
|
|
snd_pcm_hw_params_t* pars;
|
|
snd_pcm_hw_params_alloca(&pars);
|
|
|
|
unsigned min, max;
|
|
int err;
|
|
snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
int card = -1;
|
|
while (::snd_card_next(&card) == 0 && card >= 0) {
|
|
char hwdev[80];
|
|
::snprintf(hwdev, sizeof(hwdev), "hw:%d", card);
|
|
|
|
if (::snd_ctl_open(&handle, hwdev, 0) < 0)
|
|
continue;
|
|
|
|
::snd_ctl_card_info(handle, info);
|
|
::snd_ctl_card_info_get_name(info);
|
|
|
|
int dev = -1;
|
|
while (::snd_ctl_pcm_next_device(handle, &dev) == 0 && dev >= 0) {
|
|
::snd_pcm_info_set_device(pcminfo, dev);
|
|
::snd_pcm_info_set_subdevice(pcminfo, 0);
|
|
::snd_pcm_info_set_stream(pcminfo, stream);
|
|
|
|
err = ::snd_ctl_pcm_info(handle, pcminfo);
|
|
if (err != -ENOENT) {
|
|
::snprintf(hwdev, sizeof(hwdev), "hw:%d,%d", card, dev);
|
|
|
|
if (::snd_pcm_open(&pcm, hwdev, stream, SND_PCM_NONBLOCK) < 0)
|
|
continue;
|
|
|
|
::snd_pcm_hw_params_any(pcm, pars);
|
|
::snd_pcm_hw_params_get_channels_min(pars, &min);
|
|
::snd_pcm_hw_params_get_channels_max(pars, &max);
|
|
|
|
::snd_pcm_hw_params_get_rate_min(pars, &min, nullptr);
|
|
::snd_pcm_hw_params_get_rate_max(pars, &max, nullptr);
|
|
|
|
::snprintf(NameString, sizeof(NameString), "hw:%d,%d %s(%s)",
|
|
card, dev,
|
|
::snd_pcm_info_get_name(pcminfo),
|
|
::snd_ctl_card_info_get_name(info));
|
|
|
|
devices.push_back(std::string(NameString));
|
|
|
|
::snd_pcm_close(pcm);
|
|
pcm = nullptr;
|
|
}
|
|
}
|
|
|
|
::snd_ctl_close(handle);
|
|
}
|
|
|
|
return devices;
|
|
}
|
|
|
|
void CSoundCardReaderWriter::setCallback(IAudioCallback* callback, int id)
|
|
{
|
|
assert(callback != nullptr);
|
|
|
|
m_callback = callback;
|
|
|
|
m_id = id;
|
|
}
|
|
|
|
bool CSoundCardReaderWriter::open()
|
|
{
|
|
int err = 0;
|
|
|
|
char buf1[100];
|
|
char buf2[100];
|
|
char* ptr;
|
|
|
|
// Store the opened devices because ALSA won't enumerate them
|
|
m_readDevices.push_back(m_readDevice);
|
|
m_writeDevices.push_back(m_writeDevice);
|
|
|
|
::strncpy(buf1, m_writeDevice.c_str(), sizeof(buf1) - 1);
|
|
buf1[sizeof(buf1) - 1] = '\0';
|
|
::strncpy(buf2, m_readDevice.c_str(), sizeof(buf2) - 1);
|
|
buf2[sizeof(buf2) - 1] = '\0';
|
|
|
|
ptr = ::strchr(buf1, ' ');
|
|
if (ptr) *ptr = 0; // Get Device part of name
|
|
|
|
ptr = ::strchr(buf2, ' ');
|
|
if (ptr) *ptr = 0; // Get Device part of name
|
|
|
|
std::string writeDevice(buf1);
|
|
std::string readDevice(buf2);
|
|
|
|
snd_pcm_t* playHandle = nullptr;
|
|
if ((err = ::snd_pcm_open(&playHandle, buf1, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
|
wxLogError("Cannot open playback audio device %s (%s)", writeDevice.c_str(), ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
snd_pcm_hw_params_t* hw_params;
|
|
if ((err = ::snd_pcm_hw_params_malloc(&hw_params)) < 0) {
|
|
wxLogError("Cannot allocate hardware parameter structure (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_any(playHandle, hw_params)) < 0) {
|
|
wxLogError("Cannot initialize hardware parameter structure (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_access(playHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
|
wxLogError("Cannot set access type (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_format(playHandle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
|
|
wxLogError("Cannot set sample format (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_rate(playHandle, hw_params, m_sampleRate, 0)) < 0) {
|
|
wxLogError("Cannot set sample rate (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
unsigned int playChannels = 1U;
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_channels(playHandle, hw_params, 1)) < 0) {
|
|
playChannels = 2U;
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_channels(playHandle, hw_params, 2)) < 0) {
|
|
wxLogError("Cannot play set channel count (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params(playHandle, hw_params)) < 0) {
|
|
wxLogError("Cannot set parameters (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
::snd_pcm_hw_params_free(hw_params);
|
|
|
|
if ((err = ::snd_pcm_prepare(playHandle)) < 0) {
|
|
wxLogError("Cannot prepare audio interface for use (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
// Open Capture
|
|
snd_pcm_t* recHandle = nullptr;
|
|
if ((err = ::snd_pcm_open(&recHandle, buf2, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
|
wxLogError("Cannot open capture audio device %s (%s)", readDevice.c_str(), ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_malloc(&hw_params)) < 0) {
|
|
wxLogError("Cannot allocate hardware parameter structure (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_any(recHandle, hw_params)) < 0) {
|
|
wxLogError("Cannot initialize hardware parameter structure (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_access(recHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
|
wxLogError("Cannot set access type (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_format(recHandle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
|
|
wxLogError("Cannot set sample format (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_rate(recHandle, hw_params, m_sampleRate, 0)) < 0) {
|
|
wxLogError("Cannot set sample rate (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
unsigned int recChannels = 1U;
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_channels(recHandle, hw_params, 1)) < 0) {
|
|
recChannels = 2U;
|
|
|
|
if ((err = ::snd_pcm_hw_params_set_channels(recHandle, hw_params, 2)) < 0) {
|
|
wxLogError("Cannot rec set channel count (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((err = ::snd_pcm_hw_params(recHandle, hw_params)) < 0) {
|
|
wxLogError("Cannot set parameters (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
::snd_pcm_hw_params_free(hw_params);
|
|
|
|
if ((err = ::snd_pcm_prepare(recHandle)) < 0) {
|
|
wxLogError("Cannot prepare audio interface for use (%s)", ::snd_strerror(err));
|
|
return false;
|
|
}
|
|
|
|
short samples[256];
|
|
for (unsigned int i = 0U; i < 10U; ++i)
|
|
::snd_pcm_readi(recHandle, samples, 128);
|
|
|
|
wxLogMessage("Opened %s %s Rate %u", writeDevice.c_str(), readDevice.c_str(), m_sampleRate);
|
|
|
|
m_reader = new CSoundCardReader(recHandle, m_blockSize, recChannels, m_callback, m_id);
|
|
m_writer = new CSoundCardWriter(playHandle, m_blockSize, playChannels, m_callback, m_id);
|
|
|
|
m_reader->start();
|
|
m_writer->start();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSoundCardReaderWriter::close()
|
|
{
|
|
if (m_reader != nullptr) {
|
|
m_reader->kill();
|
|
m_reader->join();
|
|
}
|
|
|
|
if (m_writer != nullptr) {
|
|
m_writer->kill();
|
|
m_writer->join();
|
|
}
|
|
}
|
|
|
|
bool CSoundCardReaderWriter::isWriterBusy() const
|
|
{
|
|
return m_writer->isBusy();
|
|
}
|
|
|
|
CSoundCardReader::CSoundCardReader(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback, int id) :
|
|
m_handle(handle),
|
|
m_blockSize(blockSize),
|
|
m_channels(channels),
|
|
m_callback(callback),
|
|
m_id(id),
|
|
m_killed(false),
|
|
m_buffer(nullptr),
|
|
m_samples(nullptr),
|
|
m_thread()
|
|
{
|
|
assert(handle != nullptr);
|
|
assert(blockSize > 0U);
|
|
assert(channels == 1U || channels == 2U);
|
|
assert(callback != nullptr);
|
|
|
|
m_buffer = new float[blockSize];
|
|
m_samples = new short[2U * blockSize];
|
|
}
|
|
|
|
CSoundCardReader::~CSoundCardReader()
|
|
{
|
|
delete[] m_buffer;
|
|
delete[] m_samples;
|
|
}
|
|
|
|
void CSoundCardReader::start()
|
|
{
|
|
m_thread = std::thread(&CSoundCardReader::entry, this);
|
|
}
|
|
|
|
void CSoundCardReader::entry()
|
|
{
|
|
wxLogMessage("Starting ALSA reader thread");
|
|
|
|
while (!m_killed) {
|
|
snd_pcm_sframes_t ret;
|
|
while ((ret = ::snd_pcm_readi(m_handle, m_samples, m_blockSize)) < 0) {
|
|
if (ret != -EPIPE) {
|
|
wxLogWarning("snd_pcm_readi returned %d (%s)", (int)ret, ::snd_strerror(ret));
|
|
}
|
|
|
|
::snd_pcm_recover(m_handle, ret, 1);
|
|
}
|
|
|
|
if (m_channels == 1U) {
|
|
for (int n = 0; n < ret; n++)
|
|
m_buffer[n] = float(m_samples[n]) / 32768.0F;
|
|
} else {
|
|
int i = 0;
|
|
for (int n = 0; n < (ret * 2); n += 2)
|
|
m_buffer[i++] = float(m_samples[n + 1]) / 32768.0F;
|
|
}
|
|
|
|
m_callback->readCallback(m_buffer, (unsigned int)ret, m_id);
|
|
}
|
|
|
|
wxLogMessage("Stopping ALSA reader thread");
|
|
|
|
::snd_pcm_close(m_handle);
|
|
}
|
|
|
|
void CSoundCardReader::kill()
|
|
{
|
|
m_killed = true;
|
|
}
|
|
|
|
void CSoundCardReader::join()
|
|
{
|
|
if (m_thread.joinable())
|
|
m_thread.join();
|
|
}
|
|
|
|
CSoundCardWriter::CSoundCardWriter(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback, int id) :
|
|
m_handle(handle),
|
|
m_blockSize(blockSize),
|
|
m_channels(channels),
|
|
m_callback(callback),
|
|
m_id(id),
|
|
m_killed(false),
|
|
m_buffer(nullptr),
|
|
m_samples(nullptr),
|
|
m_thread()
|
|
{
|
|
assert(handle != nullptr);
|
|
assert(blockSize > 0U);
|
|
assert(channels == 1U || channels == 2U);
|
|
assert(callback != nullptr);
|
|
|
|
m_buffer = new float[2U * blockSize];
|
|
m_samples = new short[4U * blockSize];
|
|
}
|
|
|
|
CSoundCardWriter::~CSoundCardWriter()
|
|
{
|
|
delete[] m_buffer;
|
|
delete[] m_samples;
|
|
}
|
|
|
|
void CSoundCardWriter::start()
|
|
{
|
|
m_thread = std::thread(&CSoundCardWriter::entry, this);
|
|
}
|
|
|
|
void CSoundCardWriter::entry()
|
|
{
|
|
wxLogMessage("Starting ALSA writer thread");
|
|
|
|
while (!m_killed) {
|
|
int nSamples = 2U * m_blockSize;
|
|
m_callback->writeCallback(m_buffer, nSamples, m_id);
|
|
|
|
if (nSamples == 0U) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
} else {
|
|
if (m_channels == 1U) {
|
|
for (int n = 0U; n < nSamples; n++)
|
|
m_samples[n] = short(m_buffer[n] * 32767.0F);
|
|
} else {
|
|
int i = 0U;
|
|
for (int n = 0U; n < nSamples; n++) {
|
|
short sample = short(m_buffer[n] * 32767.0F);
|
|
m_samples[i++] = sample;
|
|
m_samples[i++] = sample; // Same value to both channels
|
|
}
|
|
}
|
|
|
|
int offset = 0U;
|
|
snd_pcm_sframes_t ret;
|
|
while ((ret = ::snd_pcm_writei(m_handle, m_samples + offset, nSamples - offset)) != (nSamples - offset)) {
|
|
if (ret < 0) {
|
|
if (ret != -EPIPE) {
|
|
wxLogWarning("snd_pcm_writei returned %d (%s)", (int)ret, ::snd_strerror(ret));
|
|
}
|
|
|
|
::snd_pcm_recover(m_handle, ret, 1);
|
|
} else {
|
|
offset += ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wxLogMessage("Stopping ALSA writer thread");
|
|
|
|
::snd_pcm_close(m_handle);
|
|
}
|
|
|
|
void CSoundCardWriter::kill()
|
|
{
|
|
m_killed = true;
|
|
}
|
|
|
|
void CSoundCardWriter::join()
|
|
{
|
|
if (m_thread.joinable())
|
|
m_thread.join();
|
|
}
|
|
|
|
bool CSoundCardWriter::isBusy() const
|
|
{
|
|
snd_pcm_state_t state = ::snd_pcm_state(m_handle);
|
|
|
|
return state == SND_PCM_STATE_RUNNING || state == SND_PCM_STATE_DRAINING;
|
|
}
|
|
|
|
#else
|
|
|
|
// PortAudio implementation for Windows and macOS
|
|
|
|
static int paCallback(const void* input, void* output, unsigned long frameCount,
|
|
const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* userData)
|
|
{
|
|
CSoundCardReaderWriter* rw = static_cast<CSoundCardReaderWriter*>(userData);
|
|
rw->callback(static_cast<const float*>(input), static_cast<float*>(output), frameCount);
|
|
return paContinue;
|
|
}
|
|
|
|
CSoundCardReaderWriter::CSoundCardReaderWriter(const std::string& readDevice, const std::string& writeDevice, unsigned int sampleRate, unsigned int blockSize) :
|
|
m_readDevice(readDevice),
|
|
m_writeDevice(writeDevice),
|
|
m_sampleRate(sampleRate),
|
|
m_blockSize(blockSize),
|
|
m_callback(nullptr),
|
|
m_id(-1),
|
|
m_stream(nullptr)
|
|
{
|
|
}
|
|
|
|
CSoundCardReaderWriter::~CSoundCardReaderWriter()
|
|
{
|
|
}
|
|
|
|
void CSoundCardReaderWriter::setCallback(IAudioCallback* callback, int id)
|
|
{
|
|
m_callback = callback;
|
|
m_id = id;
|
|
}
|
|
|
|
bool CSoundCardReaderWriter::convertNameToDevices(PaDeviceIndex& inDev, PaDeviceIndex& outDev)
|
|
{
|
|
inDev = paNoDevice;
|
|
outDev = paNoDevice;
|
|
|
|
int count = Pa_GetDeviceCount();
|
|
for (int i = 0; i < count; i++) {
|
|
const PaDeviceInfo* info = Pa_GetDeviceInfo(i);
|
|
if (info == nullptr)
|
|
continue;
|
|
|
|
std::string name(info->name);
|
|
if (name == m_readDevice && info->maxInputChannels > 0)
|
|
inDev = i;
|
|
if (name == m_writeDevice && info->maxOutputChannels > 0)
|
|
outDev = i;
|
|
}
|
|
|
|
return inDev != paNoDevice && outDev != paNoDevice;
|
|
}
|
|
|
|
bool CSoundCardReaderWriter::open()
|
|
{
|
|
PaError err = Pa_Initialize();
|
|
if (err != paNoError) {
|
|
wxLogError("Pa_Initialize failed: %s", Pa_GetErrorText(err));
|
|
return false;
|
|
}
|
|
|
|
PaDeviceIndex inDev, outDev;
|
|
if (!convertNameToDevices(inDev, outDev)) {
|
|
wxLogError("Could not find PortAudio devices: read='%s' write='%s'",
|
|
m_readDevice.c_str(), m_writeDevice.c_str());
|
|
Pa_Terminate();
|
|
return false;
|
|
}
|
|
|
|
PaStreamParameters inParams, outParams;
|
|
inParams.device = inDev;
|
|
inParams.channelCount = 1;
|
|
inParams.sampleFormat = paFloat32;
|
|
inParams.suggestedLatency = Pa_GetDeviceInfo(inDev)->defaultLowInputLatency;
|
|
inParams.hostApiSpecificStreamInfo = nullptr;
|
|
|
|
outParams.device = outDev;
|
|
outParams.channelCount = 1;
|
|
outParams.sampleFormat = paFloat32;
|
|
outParams.suggestedLatency = Pa_GetDeviceInfo(outDev)->defaultLowOutputLatency;
|
|
outParams.hostApiSpecificStreamInfo = nullptr;
|
|
|
|
err = Pa_OpenStream(&m_stream, &inParams, &outParams, m_sampleRate, m_blockSize, paClipOff, paCallback, this);
|
|
if (err != paNoError) {
|
|
wxLogError("Pa_OpenStream failed: %s", Pa_GetErrorText(err));
|
|
Pa_Terminate();
|
|
return false;
|
|
}
|
|
|
|
err = Pa_StartStream(m_stream);
|
|
if (err != paNoError) {
|
|
wxLogError("Pa_StartStream failed: %s", Pa_GetErrorText(err));
|
|
Pa_CloseStream(m_stream);
|
|
m_stream = nullptr;
|
|
Pa_Terminate();
|
|
return false;
|
|
}
|
|
|
|
wxLogMessage("Opened PortAudio: read='%s' write='%s' rate=%u",
|
|
m_readDevice.c_str(), m_writeDevice.c_str(), m_sampleRate);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSoundCardReaderWriter::close()
|
|
{
|
|
if (m_stream != nullptr) {
|
|
Pa_StopStream(m_stream);
|
|
Pa_CloseStream(m_stream);
|
|
m_stream = nullptr;
|
|
}
|
|
|
|
Pa_Terminate();
|
|
}
|
|
|
|
void CSoundCardReaderWriter::callback(const float* input, float* output, unsigned int nSamples)
|
|
{
|
|
if (m_callback != nullptr) {
|
|
m_callback->readCallback(input, nSamples, m_id);
|
|
int n = static_cast<int>(nSamples);
|
|
m_callback->writeCallback(output, n, m_id);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> CSoundCardReaderWriter::getReadDevices()
|
|
{
|
|
std::vector<std::string> devices;
|
|
|
|
Pa_Initialize();
|
|
|
|
int count = Pa_GetDeviceCount();
|
|
for (int i = 0; i < count; i++) {
|
|
const PaDeviceInfo* info = Pa_GetDeviceInfo(i);
|
|
if (info != nullptr && info->maxInputChannels > 0)
|
|
devices.push_back(std::string(info->name));
|
|
}
|
|
|
|
Pa_Terminate();
|
|
return devices;
|
|
}
|
|
|
|
std::vector<std::string> CSoundCardReaderWriter::getWriteDevices()
|
|
{
|
|
std::vector<std::string> devices;
|
|
|
|
Pa_Initialize();
|
|
|
|
int count = Pa_GetDeviceCount();
|
|
for (int i = 0; i < count; i++) {
|
|
const PaDeviceInfo* info = Pa_GetDeviceInfo(i);
|
|
if (info != nullptr && info->maxOutputChannels > 0)
|
|
devices.push_back(std::string(info->name));
|
|
}
|
|
|
|
Pa_Terminate();
|
|
return devices;
|
|
}
|
|
|
|
#endif
|