Merge pull request #350 from extremesecrecy/SplitIO

Split IO
This commit is contained in:
Robin Davies
2025-07-23 17:09:30 -04:00
committed by GitHub
16 changed files with 268 additions and 185 deletions
+17 -14
View File
@@ -20,7 +20,7 @@
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
*/
#include "pch.h"
#include "util.hpp"
@@ -1223,7 +1223,9 @@ namespace pipedal
{
int err;
alsa_device_name = jackServerSettings.GetAlsaInputDevice();
std::string inputName = jackServerSettings.GetAlsaInputDevice();
std::string outputName = jackServerSettings.GetAlsaOutputDevice();
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
this->bufferSize = jackServerSettings.GetBufferSize();
@@ -1232,7 +1234,7 @@ namespace pipedal
try
{
err = snd_pcm_open(&playbackHandle, alsa_device_name.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
err = snd_pcm_open(&playbackHandle, outputName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0)
{
switch (errno)
@@ -1267,7 +1269,7 @@ namespace pipedal
snd_pcm_nonblock(playbackHandle, 0);
}
err = snd_pcm_open(&captureHandle, alsa_device_name.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
err = snd_pcm_open(&captureHandle, inputName.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (err < 0)
{
@@ -1934,7 +1936,8 @@ namespace pipedal
}
}};
std::string alsaDeviceName = jackServerSettings.GetAlsaInputDevice();
std::string inDev = jackServerSettings.GetAlsaInputDevice();
std::string outDev = jackServerSettings.GetAlsaOutputDevice();
bool result = false;
try
@@ -1942,7 +1945,7 @@ namespace pipedal
int err;
for (int retry = 0; retry < 2; ++retry)
{
err = snd_pcm_open(&playbackHandle, alsaDeviceName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
err = snd_pcm_open(&playbackHandle, outDev.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0) // field report of a device that is present, but won't immediately open.
{
sleep(1);
@@ -1952,13 +1955,13 @@ namespace pipedal
}
if (err < 0)
{
throw PiPedalStateException(SS(alsaDeviceName << " playback device not found. "
<< "(" << snd_strerror(err) << ")"));
throw PiPedalStateException(SS(outDev << " playback device not found. "
<< "(" << snd_strerror(err) << ")"));
}
for (int retry = 0; retry < 15; ++retry)
{
err = snd_pcm_open(&captureHandle, alsaDeviceName.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
err = snd_pcm_open(&captureHandle, inDev.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (err == -EBUSY)
{
sleep(1);
@@ -1967,7 +1970,7 @@ namespace pipedal
break;
}
if (err < 0)
throw PiPedalStateException(SS(alsaDeviceName << " capture device not found."));
throw PiPedalStateException(SS(inDev << " capture device not found."));
if (snd_pcm_hw_params_malloc(&playbackHwParams) < 0)
{
@@ -1981,8 +1984,8 @@ namespace pipedal
snd_pcm_hw_params_any(playbackHandle, playbackHwParams);
snd_pcm_hw_params_any(captureHandle, captureHwParams);
SetPreferredAlsaFormat(alsaDeviceName, "capture", captureHandle, captureHwParams);
SetPreferredAlsaFormat(alsaDeviceName, "output", playbackHandle, playbackHwParams);
SetPreferredAlsaFormat(inDev, "capture", captureHandle, captureHwParams);
SetPreferredAlsaFormat(outDev, "output", playbackHandle, playbackHwParams);
unsigned int sampleRate = jackServerSettings.GetSampleRate();
err = snd_pcm_hw_params_set_rate_near(playbackHandle, playbackHwParams, &sampleRate, 0);
@@ -2002,13 +2005,13 @@ namespace pipedal
err = snd_pcm_hw_params_get_channels_max(playbackHwParams, &playbackChannels);
if (err < 0)
{
throw PiPedalLogicException("No outut channels.");
throw PiPedalLogicException("No output channels.");
}
unsigned int channelsMin;
err = snd_pcm_hw_params_get_channels_min(playbackHwParams, &channelsMin);
if (err < 0)
{
throw PiPedalLogicException("No outut channels.");
throw PiPedalLogicException("No output channels.");
}
if (ShouldForceStereoChannels(playbackHandle, playbackHwParams, channelsMin, playbackChannels))
{
+1 -1
View File
@@ -20,7 +20,7 @@
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
*/
#pragma once
+2
View File
@@ -517,6 +517,8 @@ namespace pipedal
result.sampleRates_.push_back(48000);
result.minBufferSize_ = 16;
result.maxBufferSize_ = 1024;
result.supportsCapture_ = true;
result.supportsPlayback_ = true;
return result;
}
+1 -1
View File
@@ -107,7 +107,7 @@ namespace pipedal
return outputAudioPorts_;
}
// replaced with AlsaSequencerConfiguration
// replaced with AlsaSequencerConfiguration
const std::vector<AlsaMidiDeviceInfo>& LegacyGetInputMidiDevices() const
{
return inputMidiDevices_;
+13 -4
View File
@@ -148,7 +148,13 @@ void JackServerSettings::ReadJackDaemonConfiguration()
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate");
this->alsaDevice_ = GetJackStringArg(argv,"-d", "--device");
// read new dual device flags, fallback on old -d/--device
std::string capDev = GetJackStringArg(argv, "-C", "--capture");
std::string playDev = GetJackStringArg(argv, "-P", "--playback");
std::string dev = "";
try { dev = GetJackStringArg(argv, "-d", "--device"); } catch(...) {}
this->alsaInputDevice_ = capDev.empty() ? dev : capDev;
this->alsaOutputDevice_ = playDev.empty() ? dev : playDev;
this->valid_ = true;
}
catch (std::exception &)
@@ -219,11 +225,13 @@ void JackServerSettings::WriteDaemonConfig()
{
output << line << endl;
}
// the style used by qjackctl. :-/
// the style used by qjackctl. :-/
// Lower to -P70 in order to allow the USB soft-irq to run at higher priority than JACK (it runs at 80).
output << "/usr/bin/jackd "
<< "-R -P70 --silent"
<< " -dalsa -d" << this->alsaDevice_
<< " -dalsa"
<< " -C" << this->GetAlsaInputDevice()
<< " -P" << this->GetAlsaOutputDevice()
<< " -r" << this->sampleRate_
<< " -p" << this->bufferSize_
<< " -n" << this->numberOfBuffers_ << " -Xseq"
@@ -246,7 +254,8 @@ JSON_MAP_REFERENCE(JackServerSettings, valid)
JSON_MAP_REFERENCE(JackServerSettings, isOnboarding)
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
JSON_MAP_REFERENCE(JackServerSettings, alsaInputDevice)
JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDevice)
JSON_MAP_REFERENCE(JackServerSettings, sampleRate)
JSON_MAP_REFERENCE(JackServerSettings, bufferSize)
JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers)
+18 -11
View File
@@ -31,18 +31,24 @@ namespace pipedal
bool isOnboarding_ = true;
bool isJackAudio_ = JACK_HOST ? true : false;
bool rebootRequired_ = false;
std::string alsaDevice_;
std::string alsaInputDevice_;
std::string alsaOutputDevice_;
uint64_t sampleRate_ = 0;
uint32_t bufferSize_ = 64;
uint32_t numberOfBuffers_ = 3;
public:
void SetAlsaInputDevice(const std::string &d){ alsaInputDevice_ = d; }
void SetAlsaOutputDevice(const std::string &d){ alsaOutputDevice_ = d; }
JackServerSettings();
JackServerSettings(
JackServerSettings(
const std::string alsaInputDevice,
uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
uint64_t sampleRate,
uint32_t bufferSize,
uint32_t numberOfBuffers)
: valid_(true),
alsaDevice_(alsaInputDevice),
alsaInputDevice_(alsaInputDevice),
alsaOutputDevice_(alsaInputDevice), // default same
sampleRate_(sampleRate),
bufferSize_(bufferSize),
numberOfBuffers_(numberOfBuffers),
@@ -53,16 +59,17 @@ namespace pipedal
uint64_t GetSampleRate() const { return sampleRate_; }
uint32_t GetBufferSize() const { return bufferSize_; }
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
const std::string &GetAlsaInputDevice() const { return alsaDevice_; }
const std::string &GetAlsaInputDevice() const { return alsaInputDevice_; }
const std::string &GetAlsaOutputDevice() const { return alsaOutputDevice_; }
void UseDummyAudioDevice() {
this->valid_ = true;
if (sampleRate_ == 0) sampleRate_ = 48000;
this->alsaDevice_ = "dummy:channels_2";
this->alsaInputDevice_ = "__DUMMY_AUDIO__dummy:channels_2";
this->alsaOutputDevice_ = "__DUMMY_AUDIO__dummy:channels_2";
}
bool IsDummyAudioDevice() const {
return
this->alsaDevice_.starts_with("__DUMMY_AUDIO__")
|| this->alsaDevice_.starts_with("dummy:");
return this->alsaInputDevice_.starts_with("__DUMMY_AUDIO__")
|| this->alsaOutputDevice_.starts_with("__DUMMY_AUDIO__");
}
void ReadJackDaemonConfiguration();
@@ -75,7 +82,7 @@ namespace pipedal
// this->rebootRequired_ = true;
// this->sampleRate_ = sampleRate;
// this->bufferSize_ = bufferSize;
// this->numberOfBuffers_ = numberOfBuffers;
// this->numberOfBuffers_ = numberOfBuffers;
// }
void WriteDaemonConfig(); // requires root perms.
void SetRebootRequired(bool value)
@@ -89,7 +96,7 @@ namespace pipedal
bool Equals(const JackServerSettings &other)
{
return this->alsaDevice_ == other.alsaDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_;
return this->alsaInputDevice_ == other.alsaInputDevice_ && this->alsaOutputDevice_ == other.alsaOutputDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_;
}
DECLARE_JSON_MAP(JackServerSettings);
+24 -16
View File
@@ -97,20 +97,16 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
snd_pcm_t *hDevice = nullptr;
snd_pcm_t *captureDevice = nullptr;
snd_pcm_t *playbackDevice = nullptr;
bool captureOk = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0) == 0;
bool playbackOk = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, 0) == 0;
info.supportsCapture_ = captureOk;
info.supportsPlayback_ = playbackOk;
// must support capture AND playback
err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0);
if (err == 0)
{
snd_pcm_close(hDevice);
}
if (err == 0)
{
err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
}
if (err == 0)
if (captureOk || playbackOk)
{
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice;
snd_pcm_hw_params_t *params = nullptr;
err = snd_pcm_hw_params_malloc(&params);
if (err == 0)
@@ -151,15 +147,25 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
info.minBufferSize_ = (uint32_t)minBufferSize;
info.maxBufferSize_ = (uint32_t)maxBufferSize;
cacheDevice(info.name_, info);
result.push_back(info);
}
}
}
if (params != nullptr)
snd_pcm_hw_params_free(params);
snd_pcm_close(hDevice);
}
if (captureOk)
snd_pcm_close(captureDevice);
if (playbackOk && playbackDevice != captureDevice)
snd_pcm_close(playbackDevice);
if (err == 0)
{
cacheDevice(info.name_, info);
result.push_back(info);
}
else if (getCachedDevice(info.name_, &info))
{
result.push_back(info);
}
}
else
{
if (getCachedDevice(info.name_, &info))
@@ -480,6 +486,8 @@ JSON_MAP_REFERENCE(AlsaDeviceInfo, longName)
JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates)
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsCapture)
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsPlayback)
JSON_MAP_END()
JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
+2
View File
@@ -31,6 +31,8 @@ namespace pipedal {
std::string longName_;
std::vector<uint32_t> sampleRates_;
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
bool supportsCapture_ = false;
bool supportsPlayback_ = false;
bool isDummyDevice() const {
return id_.starts_with("dummy:");
+1
View File
@@ -457,6 +457,7 @@ std::string uri_builder::str() const {
s << '?';
for (size_t i = 0; i < queries_.size(); ++i)
{
if (i != 0) s << '&';
s << queries_[i].key << "=";
HtmlHelper::encode_url_segment(s,queries_[i].value,true);
}
+1 -1
View File
@@ -204,7 +204,7 @@ public:
bool has_query(const std::string &key) const {
for (size_t i = 0; i < queries_.size(); ++i)
{
if (queries_[i].key == key) true;
if (queries_[i].key == key) return true;
}
return false;
+1 -1
View File
@@ -15,7 +15,7 @@
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
+1 -1
View File
@@ -18,7 +18,7 @@
// forward declaration.
// forward declaration.
class websocket_session;