diff options
author | Chris Robinson <[email protected]> | 2021-06-08 10:52:37 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2021-06-08 10:52:37 -0700 |
commit | debb932573010fb7b260f56d618644dbadd2e6b1 (patch) | |
tree | 1664315e916e7899ed1a2b4c5c68173755f85884 /alc | |
parent | 58a9549a5886fe91aba698abb4e6e4c0c94f129e (diff) |
Add an option to mix directly in the JACK callback
Diffstat (limited to 'alc')
-rw-r--r-- | alc/alu.cpp | 102 | ||||
-rw-r--r-- | alc/backends/jack.cpp | 111 |
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; |