aboutsummaryrefslogtreecommitdiffstats
path: root/alc
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2021-06-08 10:52:37 -0700
committerChris Robinson <[email protected]>2021-06-08 10:52:37 -0700
commitdebb932573010fb7b260f56d618644dbadd2e6b1 (patch)
tree1664315e916e7899ed1a2b4c5c68173755f85884 /alc
parent58a9549a5886fe91aba698abb4e6e4c0c94f129e (diff)
Add an option to mix directly in the JACK callback
Diffstat (limited to 'alc')
-rw-r--r--alc/alu.cpp102
-rw-r--r--alc/backends/jack.cpp111
2 files changed, 144 insertions, 69 deletions
diff --git a/alc/alu.cpp b/alc/alu.cpp
index 00b91bd4..507a5de1 100644
--- a/alc/alu.cpp
+++ b/alc/alu.cpp
@@ -1925,52 +1925,78 @@ void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, cons
} // namespace
-void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep)
+uint DeviceBase::renderSamples(const uint numSamples)
{
- FPUCtl mixer_mode{};
- for(uint written{0u};written < numSamples;)
- {
- const uint samplesToDo{minu(numSamples-written, BufferLineSize)};
+ const uint samplesToDo{minu(numSamples, BufferLineSize)};
- /* Clear main mixing buffers. */
- for(FloatBufferLine &buffer : MixBuffer)
- buffer.fill(0.0f);
+ /* Clear main mixing buffers. */
+ for(FloatBufferLine &buffer : MixBuffer)
+ buffer.fill(0.0f);
- /* Increment the mix count at the start (lsb should now be 1). */
- IncrementRef(MixCount);
+ /* Increment the mix count at the start (lsb should now be 1). */
+ IncrementRef(MixCount);
- /* Process and mix each context's sources and effects. */
- ProcessContexts(this, samplesToDo);
+ /* Process and mix each context's sources and effects. */
+ ProcessContexts(this, samplesToDo);
- /* Increment the clock time. Every second's worth of samples is
- * converted and added to clock base so that large sample counts don't
- * overflow during conversion. This also guarantees a stable
- * conversion.
- */
- SamplesDone += samplesToDo;
- ClockBase += std::chrono::seconds{SamplesDone / Frequency};
- SamplesDone %= Frequency;
+ /* Increment the clock time. Every second's worth of samples is converted
+ * and added to clock base so that large sample counts don't overflow
+ * during conversion. This also guarantees a stable conversion.
+ */
+ SamplesDone += samplesToDo;
+ ClockBase += std::chrono::seconds{SamplesDone / Frequency};
+ SamplesDone %= Frequency;
+
+ /* Increment the mix count at the end (lsb should now be 0). */
+ IncrementRef(MixCount);
- /* Increment the mix count at the end (lsb should now be 0). */
- IncrementRef(MixCount);
+ /* Apply any needed post-process for finalizing the Dry mix to the RealOut
+ * (Ambisonic decode, UHJ encode, etc).
+ */
+ postProcess(samplesToDo);
- /* Apply any needed post-process for finalizing the Dry mix to the
- * RealOut (Ambisonic decode, UHJ encode, etc).
- */
- postProcess(samplesToDo);
+ /* Apply compression, limiting sample amplitude if needed or desired. */
+ if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data());
- /* Apply compression, limiting sample amplitude if needed or desired. */
- if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data());
+ /* Apply delays and attenuation for mismatched speaker distances. */
+ if(ChannelDelays)
+ ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data());
- /* Apply delays and attenuation for mismatched speaker distances. */
- if(ChannelDelays)
- ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data());
+ /* Apply dithering. The compressor should have left enough headroom for the
+ * dither noise to not saturate.
+ */
+ if(DitherDepth > 0.0f)
+ ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo);
- /* Apply dithering. The compressor should have left enough headroom for
- * the dither noise to not saturate.
- */
- if(DitherDepth > 0.0f)
- ApplyDither(RealOut.Buffer, &DitherSeed, DitherDepth, samplesToDo);
+ return samplesToDo;
+}
+
+void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint numSamples)
+{
+ FPUCtl mixer_mode{};
+ uint total{0};
+ while(const uint todo{numSamples - total})
+ {
+ const uint samplesToDo{renderSamples(todo)};
+
+ auto *srcbuf = RealOut.Buffer.data();
+ for(auto *dstbuf : outBuffers)
+ {
+ std::copy_n(srcbuf->data(), samplesToDo, dstbuf + total);
+ ++srcbuf;
+ }
+
+ total += samplesToDo;
+ }
+}
+
+void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep)
+{
+ FPUCtl mixer_mode{};
+ uint total{0};
+ while(const uint todo{numSamples - total})
+ {
+ const uint samplesToDo{renderSamples(todo)};
if LIKELY(outBuffer)
{
@@ -1980,7 +2006,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz
switch(FmtType)
{
#define HANDLE_WRITE(T) case T: \
- Write<T>(RealOut.Buffer, outBuffer, written, samplesToDo, frameStep); break;
+ Write<T>(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break;
HANDLE_WRITE(DevFmtByte)
HANDLE_WRITE(DevFmtUByte)
HANDLE_WRITE(DevFmtShort)
@@ -1992,7 +2018,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz
}
}
- written += samplesToDo;
+ total += samplesToDo;
}
}
diff --git a/alc/backends/jack.cpp b/alc/backends/jack.cpp
index 54bd19e6..51be503d 100644
--- a/alc/backends/jack.cpp
+++ b/alc/backends/jack.cpp
@@ -239,6 +239,10 @@ struct JackPlayback final : public BackendBase {
JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~JackPlayback() override;
+ int processRt(jack_nframes_t numframes) noexcept;
+ static int processRtC(jack_nframes_t numframes, void *arg) noexcept
+ { return static_cast<JackPlayback*>(arg)->processRt(numframes); }
+
int process(jack_nframes_t numframes) noexcept;
static int processC(jack_nframes_t numframes, void *arg) noexcept
{ return static_cast<JackPlayback*>(arg)->process(numframes); }
@@ -259,6 +263,7 @@ struct JackPlayback final : public BackendBase {
std::mutex mMutex;
std::atomic<bool> mPlaying{false};
+ bool mRTMixing{false};
RingBufferPtr mRing;
al::semaphore mSem;
@@ -283,6 +288,30 @@ JackPlayback::~JackPlayback()
}
+int JackPlayback::processRt(jack_nframes_t numframes) noexcept
+{
+ std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
+ size_t numchans{0};
+ for(auto port : mPort)
+ {
+ if(!port || numchans == mDevice->RealOut.Buffer.size())
+ break;
+ out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
+ }
+
+ if LIKELY(mPlaying.load(std::memory_order_acquire))
+ mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
+ else
+ {
+ auto clear_buf = [numframes](float *outbuf) -> void
+ { std::fill_n(outbuf, numframes, 0.0f); };
+ std::for_each(out.begin(), out.begin()+numchans, clear_buf);
+ }
+
+ return 0;
+}
+
+
int JackPlayback::process(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
@@ -422,7 +451,9 @@ void JackPlayback::open(const char *name)
mPortPattern = iter->mPattern;
}
- jack_set_process_callback(mClient, &JackPlayback::processC, this);
+ mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", 1);
+ jack_set_process_callback(mClient,
+ mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
mDevice->DeviceName = name;
}
@@ -439,12 +470,20 @@ bool JackPlayback::reset()
*/
mDevice->Frequency = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient);
- mDevice->BufferSize = mDevice->UpdateSize * 2;
-
- const char *devname{mDevice->DeviceName.c_str()};
- uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
- bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
- mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+ if(mRTMixing)
+ {
+ /* Assume only two periods when directly mixing. Should try to query
+ * the total port latency when connected.
+ */
+ mDevice->BufferSize = mDevice->UpdateSize * 2;
+ }
+ else
+ {
+ const char *devname{mDevice->DeviceName.c_str()};
+ uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
+ bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+ mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+ }
/* Force 32-bit float output. */
mDevice->FmtType = DevFmtFloat;
@@ -460,7 +499,8 @@ bool JackPlayback::reset()
});
if(bad_port != ports_end)
{
- ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
+ ERR("Not enough JACK ports available for %s output\n",
+ DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == mPort.begin()) return false;
if(bad_port == mPort.begin()+1)
@@ -523,36 +563,45 @@ void JackPlayback::start()
mDevice->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2;
- uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
- bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
- mDevice->BufferSize = bufsize + mDevice->UpdateSize;
-
mRing = nullptr;
- mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
-
- try {
+ if(mRTMixing)
mPlaying.store(true, std::memory_order_release);
- mKillNow.store(false, std::memory_order_release);
- mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
- }
- catch(std::exception& e) {
- jack_deactivate(mClient);
- mPlaying.store(false, std::memory_order_release);
- throw al::backend_exception{al::backend_error::DeviceError,
- "Failed to start mixing thread: %s", e.what()};
+ else
+ {
+ uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
+ bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
+ mDevice->BufferSize = bufsize + mDevice->UpdateSize;
+
+ mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
+
+ try {
+ mPlaying.store(true, std::memory_order_release);
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
+ }
+ catch(std::exception& e) {
+ jack_deactivate(mClient);
+ mPlaying.store(false, std::memory_order_release);
+ throw al::backend_exception{al::backend_error::DeviceError,
+ "Failed to start mixing thread: %s", e.what()};
+ }
}
}
void JackPlayback::stop()
{
- if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
- return;
-
- mSem.post();
- mThread.join();
+ if(mPlaying.load(std::memory_order_acquire))
+ {
+ mKillNow.store(true, std::memory_order_release);
+ if(mThread.joinable())
+ {
+ mSem.post();
+ mThread.join();
+ }
- jack_deactivate(mClient);
- mPlaying.store(false, std::memory_order_release);
+ jack_deactivate(mClient);
+ mPlaying.store(false, std::memory_order_release);
+ }
}
@@ -562,7 +611,7 @@ ClockLatency JackPlayback::getClockLatency()
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
- ret.Latency = std::chrono::seconds{mRing->readSpace()};
+ ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
return ret;