Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqnxsndhelpers_p.h
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QQNXSNDHELPERS_P_H
5#define QQNXSNDHELPERS_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists for the convenience
12// of other Qt classes. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <alsa/asoundlib.h>
19
20#include <QtCore/qbytearray.h>
21#include <QtCore/qloggingcategory.h>
22#include <QtMultimedia/qaudioformat.h>
23#include <QtMultimedia/private/qaudiohelpers_p.h>
24
25#include <chrono>
26#include <cstddef>
27#include <cstdint>
28#include <memory>
29#include <optional>
30
31QT_BEGIN_NAMESPACE
32
33namespace QnxSndHelpers {
34
35// snd_pcm_resume retries before falling back to snd_pcm_prepare.
36inline constexpr int kSuspendResumeRetryLimit = 5;
37// Per-retry delay during suspend/resume recovery (ALSA cookbook idiom).
38inline constexpr std::chrono::milliseconds kSuspendResumeRetryDelay{ 100 };
39// Number of periods (chunks) per ALSA buffer: buffer = period * chunks. 2 is
40// double-buffering, 3 gives a little more slack; overridable via env var.
41inline constexpr unsigned kDefaultPeriodCount = 3;
42// Acceptable env-var override range for the period count.
43inline constexpr unsigned kMinPeriodCount = 2;
44inline constexpr unsigned kMaxPeriodCount = 16;
45// Default ALSA period size in frames, used unless the caller passes a
46// NativePeriodFrames override. ~21 ms at 48 kHz.
47inline constexpr snd_pcm_uframes_t kDefaultPeriodFrames = 1024;
48// Worker-thread real-time priority band. io-snd runs its own data/helper threads
49// at SCHED_RR 24/25 (mixer/irq higher); we stay strictly below 24 so we never
50// preempt them (raising into that band REPLY-block-wedges the driver), but above
51// the default application priority (10) so a busy app thread cannot preempt the
52// audio worker. Overridable within the band via QT_QNXSND_WORKER_PRIO.
53inline constexpr int kMinWorkerPriority = 11;
54inline constexpr int kMaxWorkerPriority = 23;
55inline constexpr int kDefaultWorkerPriority = 20;
56// Self-pipe to wake a worker blocked in poll() on a PCM fd: the worker adds
57// readFd() to its poll set; another thread calls wake() to return the poll().
58// RAII closes both fds. wake()/drain() are const (they touch the pipe, not the
59// object's own state).
61{
62public:
63 WakePipe() = default;
64 ~WakePipe();
66
67 bool open(); // create a non-blocking pipe; false on failure
68 void close() noexcept;
69 void wake() const noexcept; // write one byte (no-op if a wake is pending)
70 void drain() const noexcept; // discard all pending wake bytes
71 void waitForWake() const noexcept; // block until wake() fires, then drain
72 int readFd() const noexcept { return m_fds[0]; }
73 bool isOpen() const noexcept { return m_fds[0] >= 0; }
74
75private:
76 int m_fds[2] = { -1, -1 };
77};
78
79// Block until the PCM handle is ready for I/O (POLLOUT for playback / POLLIN for
80// capture, as set by snd_pcm_poll_descriptors) or the wake pipe is signalled:
81// Ready - the PCM reported its I/O revent; the caller may write/read a period
82// Woken - the wake pipe fired (re-check stop/suspend); the pipe is drained
83// Error - poll() failed or the PCM reported POLLERR/POLLHUP/POLLNVAL
84enum class PollOutcome { Ready, Woken, Error };
85PollOutcome pollPcm(snd_pcm_t *handle, const WakePipe &wake);
86
87// RAII guard for the void** array returned by snd_device_name_hint.
89{
90 void operator()(void **h) const noexcept
91 {
92 if (h)
93 snd_device_name_free_hint(h);
94 }
95};
96using HintsGuard = std::unique_ptr<void *, HintsDeleter>;
97
98// Map a QAudioFormat::SampleFormat to the corresponding ALSA PCM format.
99// Returns SND_PCM_FORMAT_UNKNOWN for Unknown / NSampleFormats / unhandled.
100snd_pcm_format_t mapSampleFormat(QAudioFormat::SampleFormat) noexcept;
101
102// Recover from xrun / suspend states; returns the resulting errno.
103int recoverFromXrun(snd_pcm_t *handle, int err);
104
105// Start a prepared PCM. No-op (returns 0) if it already left PREPARED, so a
106// caller can prefill-then-start without racing the device's auto-start.
107// Returns the snd_pcm_start errno otherwise.
108int startPcm(snd_pcm_t *handle);
109
110// Raise the *calling* (worker) thread to SCHED_FIFO at a priority in the
111// [kMinWorkerPriority, kMaxWorkerPriority] band, overridable via the
112// QT_QNXSND_WORKER_PRIO environment variable. On failure, logs via the supplied
113// category and leaves the thread's inherited scheduling unchanged.
114void setWorkerRealtimePriority(const QLoggingCategory &category);
115
116// Inputs for openConfiguredPcm: everything that differs between the
117// playback and capture paths.
119{
120 snd_pcm_stream_t direction; // SND_PCM_STREAM_PLAYBACK / _CAPTURE
124 const char *streamLabel; // "Playback" or "Capture", embedded in log strings
125 const char *periodCountEnvVar; // e.g. "QT_QNXSND_OUTPUT_PERIODS"; chunk-count override
126 std::optional<uint32_t> periodFrames; // NativePeriodFrames override; nullopt -> kDefaultPeriodFrames
127};
128
129// Outputs from openConfiguredPcm. handle is null on failure (use operator bool).
131{
132 snd_pcm_t *handle = nullptr;
133 snd_pcm_uframes_t periodFrames = 0;
134 snd_pcm_uframes_t bufferFrames = 0;
136 // The device's chosen native sample format (may be a 24-bit format that
137 // QAudioFormat cannot express). The stream converts between this and the
138 // application format via the base-class process()/runAudioCallback helpers.
141
142 explicit operator bool() const noexcept { return handle != nullptr; }
143};
144
145// Open a PCM device, configure hw/sw params and prepare it. The stream is left
146// PREPARED but NOT started — the worker thread starts it (see startPcm), after
147// prefilling the first period on the playback side. Returns a populated
148// PcmOpenResult on success; on failure the handle is closed internally and the
149// result evaluates to false.
151
152} // namespace QnxSndHelpers
153
154QT_END_NAMESPACE
155
156#endif // QQNXSNDHELPERS_P_H
bool isOpen() const noexcept
void drain() const noexcept
int readFd() const noexcept
void waitForWake() const noexcept
void wake() const noexcept
QAudioDevicePrivate::AudioDeviceFormat defaultDeviceFormat(QAudioDevice::Mode mode)
QAudioDevicePrivate::AudioDeviceFormat probeDeviceFormat(const QByteArray &dev, QAudioDevice::Mode mode)
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)
std::unique_ptr< void *, HintsDeleter > HintsGuard
constexpr unsigned kMinPeriodCount
constexpr std::chrono::milliseconds kSuspendResumeRetryDelay
constexpr int kMinWorkerPriority
PcmOpenResult openConfiguredPcm(const PcmOpenConfig &config)
QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQIORing)
void operator()(void **h) const noexcept
std::optional< uint32_t > periodFrames
const QLoggingCategory & category
QAudioHelperInternal::NativeSampleFormat nativeFormat