2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
34 #include "alc/alconfig.h"
35 #include "alnumeric.h"
36 #include "core/device.h"
37 #include "core/helpers.h"
38 #include "core/logging.h"
40 #include "ringbuffer.h"
43 #include <jack/jack.h>
44 #include <jack/ringbuffer.h>
50 #define JACK_FUNCS(MAGIC) \
51 MAGIC(jack_client_open); \
52 MAGIC(jack_client_close); \
53 MAGIC(jack_client_name_size); \
54 MAGIC(jack_get_client_name); \
55 MAGIC(jack_connect); \
56 MAGIC(jack_activate); \
57 MAGIC(jack_deactivate); \
58 MAGIC(jack_port_register); \
59 MAGIC(jack_port_unregister); \
60 MAGIC(jack_port_get_buffer); \
61 MAGIC(jack_port_name); \
62 MAGIC(jack_get_ports); \
64 MAGIC(jack_get_sample_rate); \
65 MAGIC(jack_set_error_function); \
66 MAGIC(jack_set_process_callback); \
67 MAGIC(jack_set_buffer_size_callback); \
68 MAGIC(jack_set_buffer_size); \
69 MAGIC(jack_get_buffer_size);
72 #define MAKE_FUNC(f) decltype(f) * p##f
74 decltype(jack_error_callback
) * pjack_error_callback
;
78 #define jack_client_open pjack_client_open
79 #define jack_client_close pjack_client_close
80 #define jack_client_name_size pjack_client_name_size
81 #define jack_get_client_name pjack_get_client_name
82 #define jack_connect pjack_connect
83 #define jack_activate pjack_activate
84 #define jack_deactivate pjack_deactivate
85 #define jack_port_register pjack_port_register
86 #define jack_port_unregister pjack_port_unregister
87 #define jack_port_get_buffer pjack_port_get_buffer
88 #define jack_port_name pjack_port_name
89 #define jack_get_ports pjack_get_ports
90 #define jack_free pjack_free
91 #define jack_get_sample_rate pjack_get_sample_rate
92 #define jack_set_error_function pjack_set_error_function
93 #define jack_set_process_callback pjack_set_process_callback
94 #define jack_set_buffer_size_callback pjack_set_buffer_size_callback
95 #define jack_set_buffer_size pjack_set_buffer_size
96 #define jack_get_buffer_size pjack_get_buffer_size
97 #define jack_error_callback (*pjack_error_callback)
102 constexpr char JackDefaultAudioType
[] = JACK_DEFAULT_AUDIO_TYPE
;
104 jack_options_t ClientOptions
= JackNullOption
;
113 std::string missing_funcs
;
116 #define JACKLIB "libjack.dll"
118 #define JACKLIB "libjack.so.0"
120 jack_handle
= LoadLib(JACKLIB
);
123 WARN("Failed to load %s\n", JACKLIB
);
128 #define LOAD_FUNC(f) do { \
129 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
130 if(p##f == nullptr) { \
132 missing_funcs += "\n" #f; \
135 JACK_FUNCS(LOAD_FUNC
);
137 /* Optional symbols. These don't exist in all versions of JACK. */
138 #define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f))
139 LOAD_SYM(jack_error_callback
);
144 WARN("Missing expected functions:%s\n", missing_funcs
.c_str());
145 CloseLib(jack_handle
);
146 jack_handle
= nullptr;
156 void operator()(void *ptr
) { jack_free(ptr
); }
158 using JackPortsPtr
= std::unique_ptr
<const char*[],JackDeleter
>;
162 std::string mPattern
;
164 template<typename T
, typename U
>
165 DeviceEntry(T
&& name
, U
&& pattern
)
166 : mName
{std::forward
<T
>(name
)}, mPattern
{std::forward
<U
>(pattern
)}
170 al::vector
<DeviceEntry
> PlaybackList
;
173 void EnumerateDevices(jack_client_t
*client
, al::vector
<DeviceEntry
> &list
)
175 std::remove_reference_t
<decltype(list
)>{}.swap(list
);
177 if(JackPortsPtr ports
{jack_get_ports(client
, nullptr, JackDefaultAudioType
, JackPortIsInput
)})
179 for(size_t i
{0};ports
[i
];++i
)
181 const char *sep
{std::strchr(ports
[i
], ':')};
182 if(!sep
|| ports
[i
] == sep
) continue;
184 const al::span
<const char> portdev
{ports
[i
], sep
};
185 auto check_name
= [portdev
](const DeviceEntry
&entry
) -> bool
187 const size_t len
{portdev
.size()};
188 return entry
.mName
.length() == len
189 && entry
.mName
.compare(0, len
, portdev
.data(), len
) == 0;
191 if(std::find_if(list
.cbegin(), list
.cend(), check_name
) != list
.cend())
194 std::string name
{portdev
.data(), portdev
.size()};
195 list
.emplace_back(name
, name
+":");
196 const auto &entry
= list
.back();
197 TRACE("Got device: %s = %s\n", entry
.mName
.c_str(), entry
.mPattern
.c_str());
199 /* There are ports but couldn't get device names from them. Add a
202 if(ports
[0] && list
.empty())
204 WARN("No device names found in available ports, adding a generic name.\n");
205 list
.emplace_back("JACK", "");
209 if(auto listopt
= ConfigValueStr(nullptr, "jack", "custom-devices"))
211 for(size_t strpos
{0};strpos
< listopt
->size();)
213 size_t nextpos
{listopt
->find(';', strpos
)};
214 size_t seppos
{listopt
->find('=', strpos
)};
215 if(seppos
>= nextpos
|| seppos
== strpos
)
217 const std::string entry
{listopt
->substr(strpos
, nextpos
-strpos
)};
218 ERR("Invalid device entry: \"%s\"\n", entry
.c_str());
219 if(nextpos
!= std::string::npos
) ++nextpos
;
224 const al::span
<const char> name
{listopt
->data()+strpos
, seppos
-strpos
};
225 const al::span
<const char> pattern
{listopt
->data()+(seppos
+1),
226 std::min(nextpos
, listopt
->size())-(seppos
+1)};
228 /* Check if this custom pattern already exists in the list. */
229 auto check_pattern
= [pattern
](const DeviceEntry
&entry
) -> bool
231 const size_t len
{pattern
.size()};
232 return entry
.mPattern
.length() == len
233 && entry
.mPattern
.compare(0, len
, pattern
.data(), len
) == 0;
235 auto itemmatch
= std::find_if(list
.begin(), list
.end(), check_pattern
);
236 if(itemmatch
!= list
.end())
238 /* If so, replace the name with this custom one. */
239 itemmatch
->mName
.assign(name
.data(), name
.size());
240 TRACE("Customized device name: %s = %s\n", itemmatch
->mName
.c_str(),
241 itemmatch
->mPattern
.c_str());
245 /* Otherwise, add a new device entry. */
246 list
.emplace_back(std::string
{name
.data(), name
.size()},
247 std::string
{pattern
.data(), pattern
.size()});
248 const auto &entry
= list
.back();
249 TRACE("Got custom device: %s = %s\n", entry
.mName
.c_str(), entry
.mPattern
.c_str());
252 if(nextpos
!= std::string::npos
) ++nextpos
;
259 /* Rename entries that have matching names, by appending '#2', '#3',
262 for(auto curitem
= list
.begin()+1;curitem
!= list
.end();++curitem
)
264 auto check_match
= [curitem
](const DeviceEntry
&entry
) -> bool
265 { return entry
.mName
== curitem
->mName
; };
266 if(std::find_if(list
.begin(), curitem
, check_match
) != curitem
)
268 std::string name
{curitem
->mName
};
270 auto check_name
= [&name
](const DeviceEntry
&entry
) -> bool
271 { return entry
.mName
== name
; };
273 name
= curitem
->mName
;
275 name
+= std::to_string(++count
);
276 } while(std::find_if(list
.begin(), curitem
, check_name
) != curitem
);
277 curitem
->mName
= std::move(name
);
284 struct JackPlayback final
: public BackendBase
{
285 JackPlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
286 ~JackPlayback() override
;
288 int processRt(jack_nframes_t numframes
) noexcept
;
289 static int processRtC(jack_nframes_t numframes
, void *arg
) noexcept
290 { return static_cast<JackPlayback
*>(arg
)->processRt(numframes
); }
292 int process(jack_nframes_t numframes
) noexcept
;
293 static int processC(jack_nframes_t numframes
, void *arg
) noexcept
294 { return static_cast<JackPlayback
*>(arg
)->process(numframes
); }
298 void open(const char *name
) override
;
299 bool reset() override
;
300 void start() override
;
301 void stop() override
;
302 ClockLatency
getClockLatency() override
;
304 std::string mPortPattern
;
306 jack_client_t
*mClient
{nullptr};
307 std::array
<jack_port_t
*,MAX_OUTPUT_CHANNELS
> mPort
{};
311 std::atomic
<bool> mPlaying
{false};
312 bool mRTMixing
{false};
316 std::atomic
<bool> mKillNow
{true};
319 DEF_NEWDEL(JackPlayback
)
322 JackPlayback::~JackPlayback()
327 auto unregister_port
= [this](jack_port_t
*port
) -> void
328 { if(port
) jack_port_unregister(mClient
, port
); };
329 std::for_each(mPort
.begin(), mPort
.end(), unregister_port
);
332 jack_client_close(mClient
);
337 int JackPlayback::processRt(jack_nframes_t numframes
) noexcept
339 std::array
<jack_default_audio_sample_t
*,MAX_OUTPUT_CHANNELS
> out
;
341 for(auto port
: mPort
)
343 if(!port
|| numchans
== mDevice
->RealOut
.Buffer
.size())
345 out
[numchans
++] = static_cast<float*>(jack_port_get_buffer(port
, numframes
));
348 if(mPlaying
.load(std::memory_order_acquire
)) [[likely
]]
349 mDevice
->renderSamples({out
.data(), numchans
}, static_cast<uint
>(numframes
));
352 auto clear_buf
= [numframes
](float *outbuf
) -> void
353 { std::fill_n(outbuf
, numframes
, 0.0f
); };
354 std::for_each(out
.begin(), out
.begin()+numchans
, clear_buf
);
361 int JackPlayback::process(jack_nframes_t numframes
) noexcept
363 std::array
<jack_default_audio_sample_t
*,MAX_OUTPUT_CHANNELS
> out
;
365 for(auto port
: mPort
)
368 out
[numchans
++] = static_cast<float*>(jack_port_get_buffer(port
, numframes
));
371 jack_nframes_t total
{0};
372 if(mPlaying
.load(std::memory_order_acquire
)) [[likely
]]
374 auto data
= mRing
->getReadVector();
375 jack_nframes_t todo
{minu(numframes
, static_cast<uint
>(data
.first
.len
))};
376 auto write_first
= [&data
,numchans
,todo
](float *outbuf
) -> float*
378 const float *RESTRICT in
= reinterpret_cast<float*>(data
.first
.buf
);
379 auto deinterlace_input
= [&in
,numchans
]() noexcept
-> float
385 std::generate_n(outbuf
, todo
, deinterlace_input
);
386 data
.first
.buf
+= sizeof(float);
387 return outbuf
+ todo
;
389 std::transform(out
.begin(), out
.begin()+numchans
, out
.begin(), write_first
);
392 todo
= minu(numframes
-total
, static_cast<uint
>(data
.second
.len
));
395 auto write_second
= [&data
,numchans
,todo
](float *outbuf
) -> float*
397 const float *RESTRICT in
= reinterpret_cast<float*>(data
.second
.buf
);
398 auto deinterlace_input
= [&in
,numchans
]() noexcept
-> float
404 std::generate_n(outbuf
, todo
, deinterlace_input
);
405 data
.second
.buf
+= sizeof(float);
406 return outbuf
+ todo
;
408 std::transform(out
.begin(), out
.begin()+numchans
, out
.begin(), write_second
);
412 mRing
->readAdvance(total
);
416 if(numframes
> total
)
418 const jack_nframes_t todo
{numframes
- total
};
419 auto clear_buf
= [todo
](float *outbuf
) -> void { std::fill_n(outbuf
, todo
, 0.0f
); };
420 std::for_each(out
.begin(), out
.begin()+numchans
, clear_buf
);
426 int JackPlayback::mixerProc()
429 althrd_setname(MIXER_THREAD_NAME
);
431 const size_t frame_step
{mDevice
->channelsFromFmt()};
433 while(!mKillNow
.load(std::memory_order_acquire
)
434 && mDevice
->Connected
.load(std::memory_order_acquire
))
436 if(mRing
->writeSpace() < mDevice
->UpdateSize
)
442 auto data
= mRing
->getWriteVector();
443 size_t todo
{data
.first
.len
+ data
.second
.len
};
444 todo
-= todo
%mDevice
->UpdateSize
;
446 const auto len1
= static_cast<uint
>(minz(data
.first
.len
, todo
));
447 const auto len2
= static_cast<uint
>(minz(data
.second
.len
, todo
-len1
));
449 std::lock_guard
<std::mutex
> _
{mMutex
};
450 mDevice
->renderSamples(data
.first
.buf
, len1
, frame_step
);
452 mDevice
->renderSamples(data
.second
.buf
, len2
, frame_step
);
453 mRing
->writeAdvance(todo
);
460 void JackPlayback::open(const char *name
)
464 const PathNamePair
&binname
= GetProcBinary();
465 const char *client_name
{binname
.fname
.empty() ? "alsoft" : binname
.fname
.c_str()};
467 jack_status_t status
;
468 mClient
= jack_client_open(client_name
, ClientOptions
, &status
, nullptr);
469 if(mClient
== nullptr)
470 throw al::backend_exception
{al::backend_error::DeviceError
,
471 "Failed to open client connection: 0x%02x", status
};
472 if((status
&JackServerStarted
))
473 TRACE("JACK server started\n");
474 if((status
&JackNameNotUnique
))
476 client_name
= jack_get_client_name(mClient
);
477 TRACE("Client name not unique, got '%s' instead\n", client_name
);
481 if(PlaybackList
.empty())
482 EnumerateDevices(mClient
, PlaybackList
);
484 if(!name
&& !PlaybackList
.empty())
486 name
= PlaybackList
[0].mName
.c_str();
487 mPortPattern
= PlaybackList
[0].mPattern
;
491 auto check_name
= [name
](const DeviceEntry
&entry
) -> bool
492 { return entry
.mName
== name
; };
493 auto iter
= std::find_if(PlaybackList
.cbegin(), PlaybackList
.cend(), check_name
);
494 if(iter
== PlaybackList
.cend())
495 throw al::backend_exception
{al::backend_error::NoDevice
,
496 "Device name \"%s\" not found", name
?name
:""};
497 mPortPattern
= iter
->mPattern
;
500 mRTMixing
= GetConfigValueBool(name
, "jack", "rt-mix", 1);
501 jack_set_process_callback(mClient
,
502 mRTMixing
? &JackPlayback::processRtC
: &JackPlayback::processC
, this);
504 mDevice
->DeviceName
= name
;
507 bool JackPlayback::reset()
509 auto unregister_port
= [this](jack_port_t
*port
) -> void
510 { if(port
) jack_port_unregister(mClient
, port
); };
511 std::for_each(mPort
.begin(), mPort
.end(), unregister_port
);
514 /* Ignore the requested buffer metrics and just keep one JACK-sized buffer
515 * ready for when requested.
517 mDevice
->Frequency
= jack_get_sample_rate(mClient
);
518 mDevice
->UpdateSize
= jack_get_buffer_size(mClient
);
521 /* Assume only two periods when directly mixing. Should try to query
522 * the total port latency when connected.
524 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
528 const char *devname
{mDevice
->DeviceName
.c_str()};
529 uint bufsize
{ConfigValueUInt(devname
, "jack", "buffer-size").value_or(mDevice
->UpdateSize
)};
530 bufsize
= maxu(NextPowerOf2(bufsize
), mDevice
->UpdateSize
);
531 mDevice
->BufferSize
= bufsize
+ mDevice
->UpdateSize
;
534 /* Force 32-bit float output. */
535 mDevice
->FmtType
= DevFmtFloat
;
538 auto ports_end
= mPort
.begin() + mDevice
->channelsFromFmt();
539 auto bad_port
= mPort
.begin();
540 while(bad_port
!= ports_end
)
542 std::string name
{"channel_" + std::to_string(++port_num
)};
543 *bad_port
= jack_port_register(mClient
, name
.c_str(), JackDefaultAudioType
,
544 JackPortIsOutput
| JackPortIsTerminal
, 0);
545 if(!*bad_port
) break;
548 if(bad_port
!= ports_end
)
550 ERR("Failed to register enough JACK ports for %s output\n",
551 DevFmtChannelsString(mDevice
->FmtChans
));
552 if(bad_port
== mPort
.begin()) return false;
554 if(bad_port
== mPort
.begin()+1)
555 mDevice
->FmtChans
= DevFmtMono
;
558 ports_end
= mPort
.begin()+2;
559 while(bad_port
!= ports_end
)
561 jack_port_unregister(mClient
, *(--bad_port
));
564 mDevice
->FmtChans
= DevFmtStereo
;
568 setDefaultChannelOrder();
573 void JackPlayback::start()
575 if(jack_activate(mClient
))
576 throw al::backend_exception
{al::backend_error::DeviceError
, "Failed to activate client"};
578 const char *devname
{mDevice
->DeviceName
.c_str()};
579 if(ConfigValueBool(devname
, "jack", "connect-ports").value_or(true))
581 JackPortsPtr pnames
{jack_get_ports(mClient
, mPortPattern
.c_str(), JackDefaultAudioType
,
585 jack_deactivate(mClient
);
586 throw al::backend_exception
{al::backend_error::DeviceError
, "No playback ports found"};
589 for(size_t i
{0};i
< al::size(mPort
) && mPort
[i
];++i
)
593 ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort
[i
]));
596 if(jack_connect(mClient
, jack_port_name(mPort
[i
]), pnames
[i
]))
597 ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort
[i
]),
602 /* Reconfigure buffer metrics in case the server changed it since the reset
603 * (it won't change again after jack_activate), then allocate the ring
604 * buffer with the appropriate size.
606 mDevice
->Frequency
= jack_get_sample_rate(mClient
);
607 mDevice
->UpdateSize
= jack_get_buffer_size(mClient
);
608 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
612 mPlaying
.store(true, std::memory_order_release
);
615 uint bufsize
{ConfigValueUInt(devname
, "jack", "buffer-size").value_or(mDevice
->UpdateSize
)};
616 bufsize
= maxu(NextPowerOf2(bufsize
), mDevice
->UpdateSize
);
617 mDevice
->BufferSize
= bufsize
+ mDevice
->UpdateSize
;
619 mRing
= RingBuffer::Create(bufsize
, mDevice
->frameSizeFromFmt(), true);
622 mPlaying
.store(true, std::memory_order_release
);
623 mKillNow
.store(false, std::memory_order_release
);
624 mThread
= std::thread
{std::mem_fn(&JackPlayback::mixerProc
), this};
626 catch(std::exception
& e
) {
627 jack_deactivate(mClient
);
628 mPlaying
.store(false, std::memory_order_release
);
629 throw al::backend_exception
{al::backend_error::DeviceError
,
630 "Failed to start mixing thread: %s", e
.what()};
635 void JackPlayback::stop()
637 if(mPlaying
.load(std::memory_order_acquire
))
639 mKillNow
.store(true, std::memory_order_release
);
640 if(mThread
.joinable())
646 jack_deactivate(mClient
);
647 mPlaying
.store(false, std::memory_order_release
);
652 ClockLatency
JackPlayback::getClockLatency()
656 std::lock_guard
<std::mutex
> _
{mMutex
};
657 ret
.ClockTime
= GetDeviceClockTime(mDevice
);
658 ret
.Latency
= std::chrono::seconds
{mRing
? mRing
->readSpace() : mDevice
->UpdateSize
};
659 ret
.Latency
/= mDevice
->Frequency
;
665 void jack_msg_handler(const char *message
)
667 WARN("%s\n", message
);
672 bool JackBackendFactory::init()
677 if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false))
678 ClientOptions
= static_cast<jack_options_t
>(ClientOptions
| JackNoStartServer
);
680 const PathNamePair
&binname
= GetProcBinary();
681 const char *client_name
{binname
.fname
.empty() ? "alsoft" : binname
.fname
.c_str()};
683 void (*old_error_cb
)(const char*){&jack_error_callback
? jack_error_callback
: nullptr};
684 jack_set_error_function(jack_msg_handler
);
685 jack_status_t status
;
686 jack_client_t
*client
{jack_client_open(client_name
, ClientOptions
, &status
, nullptr)};
687 jack_set_error_function(old_error_cb
);
690 WARN("jack_client_open() failed, 0x%02x\n", status
);
691 if((status
&JackServerFailed
) && !(ClientOptions
&JackNoStartServer
))
692 ERR("Unable to connect to JACK server\n");
696 jack_client_close(client
);
700 bool JackBackendFactory::querySupport(BackendType type
)
701 { return (type
== BackendType::Playback
); }
703 std::string
JackBackendFactory::probe(BackendType type
)
705 std::string outnames
;
706 auto append_name
= [&outnames
](const DeviceEntry
&entry
) -> void
708 /* Includes null char. */
709 outnames
.append(entry
.mName
.c_str(), entry
.mName
.length()+1);
712 const PathNamePair
&binname
= GetProcBinary();
713 const char *client_name
{binname
.fname
.empty() ? "alsoft" : binname
.fname
.c_str()};
714 jack_status_t status
;
717 case BackendType::Playback
:
718 if(jack_client_t
*client
{jack_client_open(client_name
, ClientOptions
, &status
, nullptr)})
720 EnumerateDevices(client
, PlaybackList
);
721 jack_client_close(client
);
724 WARN("jack_client_open() failed, 0x%02x\n", status
);
725 std::for_each(PlaybackList
.cbegin(), PlaybackList
.cend(), append_name
);
727 case BackendType::Capture
:
733 BackendPtr
JackBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
735 if(type
== BackendType::Playback
)
736 return BackendPtr
{new JackPlayback
{device
}};
740 BackendFactory
&JackBackendFactory::getFactory()
742 static JackBackendFactory factory
{};