6#include <QtCore/qsysinfo.h>
7#include <QtCore/qthread.h>
8#include <QtCore/qvarlengtharray.h>
32 if (::pipe(m_fds) != 0)
36 ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
42 for (
int &fd : m_fds) {
55 ssize_t n = ::write(m_fds[1], &byte, 1);
64 while (::read(m_fds[0], buf,
sizeof(buf)) > 0) { }
71 pollfd pfd = { m_fds[0], POLLIN, 0 };
74 if (::poll(&pfd, 1, -1) > 0)
80 int count = snd_pcm_poll_descriptors_count(handle);
88 constexpr int kMaxPollDescriptors = 8;
89 std::array<pollfd, kMaxPollDescriptors> fds{};
90 Q_ASSERT(count <=
int(fds.size()) - 1);
91 if (count >
int(fds.size()) - 1)
92 count =
int(fds.size()) - 1;
93 const int n = snd_pcm_poll_descriptors(handle, fds.data(), count);
98 fds[n].events = POLLIN;
100 const int rc = ::poll(fds.data(), n + 1, -1);
104 if (fds[n].revents & POLLIN) {
109 unsigned short revents = 0;
110 if (snd_pcm_poll_descriptors_revents(handle, fds.data(), n, &revents) < 0)
112 if (revents & (POLLERR | POLLNVAL | POLLHUP))
114 if (revents & (POLLIN | POLLOUT))
123 constexpr bool isBigEndian = QSysInfo::ByteOrder == QSysInfo::BigEndian;
126 case QAudioFormat::UInt8:
127 return SND_PCM_FORMAT_U8;
128 case QAudioFormat::Int16:
129 return isBigEndian ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE;
130 case QAudioFormat::Int32:
131 return isBigEndian ? SND_PCM_FORMAT_S32_BE : SND_PCM_FORMAT_S32_LE;
132 case QAudioFormat::Float:
133 return isBigEndian ? SND_PCM_FORMAT_FLOAT_BE : SND_PCM_FORMAT_FLOAT_LE;
134 case QAudioFormat::Unknown:
135 case QAudioFormat::NSampleFormats:
138 return SND_PCM_FORMAT_UNKNOWN;
145snd_pcm_format_t nativeToPcmFormat(QAudioHelperInternal::NativeSampleFormat fmt)
noexcept
147 using QAudioHelperInternal::NativeSampleFormat;
148 constexpr bool isBigEndian = QSysInfo::ByteOrder == QSysInfo::BigEndian;
151 case NativeSampleFormat::uint8_t:
152 return SND_PCM_FORMAT_U8;
153 case NativeSampleFormat::int16_t:
154 return isBigEndian ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE;
155 case NativeSampleFormat::int24_t_3b:
156 return isBigEndian ? SND_PCM_FORMAT_S24_3BE : SND_PCM_FORMAT_S24_3LE;
157 case NativeSampleFormat::int24_t_4b_low:
158 return isBigEndian ? SND_PCM_FORMAT_S24_BE : SND_PCM_FORMAT_S24_LE;
159 case NativeSampleFormat::int32_t:
160 return isBigEndian ? SND_PCM_FORMAT_S32_BE : SND_PCM_FORMAT_S32_LE;
161 case NativeSampleFormat::float32_t:
162 return isBigEndian ? SND_PCM_FORMAT_FLOAT_BE : SND_PCM_FORMAT_FLOAT_LE;
164 return SND_PCM_FORMAT_UNKNOWN;
172int negotiateNativeFormat(snd_pcm_t *handle, snd_pcm_hw_params_t *hwparams,
173 const QAudioFormat &format,
const QLoggingCategory &category,
174 QAudioHelperInternal::NativeSampleFormat &chosen)
176 using QAudioHelperInternal::NativeSampleFormat;
179 static constexpr NativeSampleFormat candidates[] = {
180 NativeSampleFormat::uint8_t, NativeSampleFormat::int16_t,
181 NativeSampleFormat::int24_t_3b, NativeSampleFormat::int24_t_4b_low,
182 NativeSampleFormat::int32_t, NativeSampleFormat::float32_t,
185 QVarLengthArray<NativeSampleFormat, std::size(candidates)> supported;
186 for (NativeSampleFormat nf : candidates) {
187 if (snd_pcm_hw_params_test_format(handle, hwparams, nativeToPcmFormat(nf)) == 0)
188 supported.append(nf);
190 if (supported.isEmpty()) {
191 qCWarning(category) <<
"device supports no usable native sample format";
195 chosen = QAudioHelperInternal::bestNativeSampleFormat(format, supported);
196 qCDebug(category) <<
"native format: requested" << format.sampleFormat()
197 <<
"device-supported" << supported <<
"chosen" << chosen;
198 return snd_pcm_hw_params_set_format(handle, hwparams, nativeToPcmFormat(chosen));
211 err = snd_pcm_prepare(handle);
212 }
else if ((err == -estrpipe) || (err == -EIO)) {
215 while ((err = snd_pcm_resume(handle)) == -EAGAIN) {
216 QThread::msleep(kSuspendResumeRetryDelay.count());
221 err = snd_pcm_prepare(handle);
229unsigned periodCountFromEnv(
const char *name,
const QLoggingCategory &category)
232 const int value = qEnvironmentVariableIntValue(name, &ok);
236 qCWarning(category) << name <<
"value" << value <<
"is outside ["
241 return static_cast<
unsigned>(value);
249 if (snd_pcm_state(handle) != SND_PCM_STATE_PREPARED)
252 const int err = snd_pcm_start(handle);
253 return (err < 0 && err != -EAGAIN) ? err : 0;
260 if (
const int value = qEnvironmentVariableIntValue(
"QT_QNXSND_WORKER_PRIO", &ok); ok) {
262 qCWarning(category) <<
"QT_QNXSND_WORKER_PRIO value" << value <<
"is outside ["
270 sched_param param = {};
271 param.sched_priority = prio;
276 if (
const int rc = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); rc != 0) {
277 qCWarning(category) <<
"could not set worker SCHED_FIFO priority" << prio <<
":"
278 << qt_error_string(rc) <<
"- keeping inherited scheduling";
281 qCDebug(category) <<
"worker thread running SCHED_FIFO at priority" << prio;
286 const QLoggingCategory &category = config.category;
291 <<
"device:" << config.deviceId;
294 snd_pcm_t *handle =
nullptr;
301 int err = snd_pcm_open(&handle, config.deviceId.constData(), config
.direction,
303 if (err < 0 || handle ==
nullptr) {
304 qCWarning(category) <<
"Failed to open" << config
.streamLabel
305 <<
"device:" << config.deviceId
306 <<
"error:" << snd_strerror(err);
311 qCWarning(category) << config
.streamLabel <<
":" << what
312 <<
"err =" << snd_strerror(err);
313 snd_pcm_close(handle);
317 snd_pcm_hw_params_t *hwparams;
318 snd_pcm_hw_params_alloca(&hwparams);
320 unsigned int sampleRate = config.format.sampleRate();
322 if ((err = snd_pcm_hw_params_any(handle, hwparams)) < 0)
323 return fail(
"snd_pcm_hw_params_any");
325 if (snd_pcm_hw_params_set_rate_resample(handle, hwparams, 1) < 0) {
328 qCDebug(category) <<
"snd_pcm_hw_params_set_rate_resample not supported, continuing";
331 if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
332 return fail(
"snd_pcm_hw_params_set_access");
334 if ((err = negotiateNativeFormat(handle, hwparams, config.format, category,
335 result.nativeFormat)) < 0)
336 return fail(
"snd_pcm_hw_params_set_format");
338 if ((err = snd_pcm_hw_params_set_channels(
340 static_cast<
unsigned int>(config.format.channelCount()))) < 0)
341 return fail(
"snd_pcm_hw_params_set_channels");
343 if ((err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &sampleRate, 0)) < 0)
344 return fail(
"snd_pcm_hw_params_set_rate_near");
349 if ((err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &periodFrames, &dir)) < 0)
350 return fail(
"snd_pcm_hw_params_set_period_size_near");
352 unsigned int chunks = periodCountFromEnv(config.periodCountEnvVar, category);
353 if ((err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &chunks, &dir)) < 0)
354 return fail(
"snd_pcm_hw_params_set_periods_near");
356 if ((err = snd_pcm_hw_params(handle, hwparams)) < 0)
357 return fail(
"snd_pcm_hw_params");
359 if ((err = snd_pcm_hw_params_get_buffer_size(hwparams, &result
.bufferFrames)) < 0)
360 return fail(
"snd_pcm_hw_params_get_buffer_size");
361 if ((err = snd_pcm_hw_params_get_period_size(hwparams, &result
.periodFrames, &dir)) < 0)
362 return fail(
"snd_pcm_hw_params_get_period_size");
363 result.periodBytes = snd_pcm_frames_to_bytes(handle, result
.periodFrames);
366 return fail(
"invalid period geometry");
369 snd_pcm_sw_params_t *swparams;
370 snd_pcm_sw_params_alloca(&swparams);
372 if ((err = snd_pcm_sw_params_current(handle, swparams)) < 0)
373 return fail(
"snd_pcm_sw_params_current");
377 if ((err = snd_pcm_sw_params_set_avail_min(handle, swparams, result
.periodFrames)) < 0)
378 return fail(
"snd_pcm_sw_params_set_avail_min");
379 if ((err = snd_pcm_sw_params(handle, swparams)) < 0)
380 return fail(
"snd_pcm_sw_params");
382 if ((err = snd_pcm_prepare(handle)) < 0)
383 return fail(
"snd_pcm_prepare");
387 qCDebug(category) << config
.streamLabel <<
"opened:" << config.deviceId
390 <<
"period_bytes:" << result.periodBytes
391 <<
"chunks:" << chunks;
bool isOpen() const noexcept
void drain() const noexcept
int readFd() const noexcept
void waitForWake() const noexcept
void wake() const noexcept
constexpr unsigned kDefaultPeriodCount
void setWorkerRealtimePriority(const QLoggingCategory &category)
constexpr unsigned kMaxPeriodCount
constexpr int kDefaultWorkerPriority
int startPcm(snd_pcm_t *handle)
snd_pcm_format_t mapSampleFormat(QAudioFormat::SampleFormat) noexcept
constexpr int kSuspendResumeRetryLimit
constexpr snd_pcm_uframes_t kDefaultPeriodFrames
int recoverFromXrun(snd_pcm_t *handle, int err)
constexpr int kMaxWorkerPriority
PollOutcome pollPcm(snd_pcm_t *handle, const WakePipe &wake)
constexpr unsigned kMinPeriodCount
constexpr int kMinWorkerPriority
PcmOpenResult openConfiguredPcm(const PcmOpenConfig &config)
snd_pcm_stream_t direction
snd_pcm_uframes_t bufferFrames
snd_pcm_uframes_t periodFrames