Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qcoreaudioutils.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qcoreaudioutils_p.h"
5#include <qdebug.h>
6#include <mach/mach_time.h>
7
9
10double CoreAudioUtils::sFrequency = 0.0;
11bool CoreAudioUtils::sIsInitialized = false;
12
13void CoreAudioUtils::initialize()
14{
15 struct mach_timebase_info timeBaseInfo;
16 mach_timebase_info(&timeBaseInfo);
17 sFrequency = static_cast<double>(timeBaseInfo.denom) / static_cast<double>(timeBaseInfo.numer);
18 sFrequency *= 1000000000.0;
19
20 sIsInitialized = true;
21}
22
23
25{
26 return mach_absolute_time();
27}
28
30{
31 if (!sIsInitialized)
32 initialize();
33 return sFrequency;
34}
35
36QAudioFormat CoreAudioUtils::toQAudioFormat(AudioStreamBasicDescription const& sf)
37{
38 QAudioFormat audioFormat;
39 // all Darwin HW is little endian, we ignore those formats
40 if ((sf.mFormatFlags & kAudioFormatFlagIsBigEndian) != 0 && QSysInfo::ByteOrder != QSysInfo::LittleEndian)
41 return audioFormat;
42
43 // filter out the formats we're interested in
45 switch (sf.mBitsPerChannel) {
46 case 8:
47 if ((sf.mFormatFlags & kAudioFormatFlagIsSignedInteger) == 0)
49 break;
50 case 16:
51 if ((sf.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0)
53 break;
54 case 32:
55 if ((sf.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0)
57 else if ((sf.mFormatFlags & kAudioFormatFlagIsFloat) != 0)
59 break;
60 default:
61 break;
62 }
63
64 audioFormat.setSampleFormat(format);
65 audioFormat.setSampleRate(sf.mSampleRate);
66 audioFormat.setChannelCount(sf.mChannelsPerFrame);
67
68 return audioFormat;
69}
70
71AudioStreamBasicDescription CoreAudioUtils::toAudioStreamBasicDescription(QAudioFormat const& audioFormat)
72{
73 AudioStreamBasicDescription sf;
74
75 sf.mFormatFlags = kAudioFormatFlagIsPacked;
76 sf.mSampleRate = audioFormat.sampleRate();
77 sf.mFramesPerPacket = 1;
78 sf.mChannelsPerFrame = audioFormat.channelCount();
79 sf.mBitsPerChannel = audioFormat.bytesPerSample() * 8;
80 sf.mBytesPerFrame = audioFormat.bytesPerFrame();
81 sf.mBytesPerPacket = sf.mFramesPerPacket * sf.mBytesPerFrame;
82 sf.mFormatID = kAudioFormatLinearPCM;
83
84 switch (audioFormat.sampleFormat()) {
87 sf.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
88 break;
90 sf.mFormatFlags |= kAudioFormatFlagIsFloat;
91 break;
93 /* default */
96 break;
97 }
98
99 return sf;
100}
101
102
103static constexpr struct {
105 AudioChannelLabel label;
106} channelMap[] = {
107 { QAudioFormat::FrontLeft, kAudioChannelLabel_Left },
108 { QAudioFormat::FrontRight, kAudioChannelLabel_Right },
109 { QAudioFormat::FrontCenter, kAudioChannelLabel_Center },
110 { QAudioFormat::LFE, kAudioChannelLabel_LFEScreen },
111 { QAudioFormat::BackLeft, kAudioChannelLabel_LeftSurround },
112 { QAudioFormat::BackRight, kAudioChannelLabel_RightSurround },
113 { QAudioFormat::FrontLeftOfCenter, kAudioChannelLabel_LeftCenter },
114 { QAudioFormat::FrontRightOfCenter, kAudioChannelLabel_RightCenter },
115 { QAudioFormat::BackCenter, kAudioChannelLabel_CenterSurround },
116 { QAudioFormat::LFE2, kAudioChannelLabel_LFE2 },
117 { QAudioFormat::SideLeft, kAudioChannelLabel_LeftSurroundDirect }, // ???
118 { QAudioFormat::SideRight, kAudioChannelLabel_RightSurroundDirect }, // ???
119 { QAudioFormat::TopFrontLeft, kAudioChannelLabel_VerticalHeightLeft },
120 { QAudioFormat::TopFrontRight, kAudioChannelLabel_VerticalHeightRight },
121 { QAudioFormat::TopFrontCenter, kAudioChannelLabel_VerticalHeightCenter },
122 { QAudioFormat::TopCenter, kAudioChannelLabel_CenterTopMiddle },
123 { QAudioFormat::TopBackLeft, kAudioChannelLabel_TopBackLeft },
124 { QAudioFormat::TopBackRight, kAudioChannelLabel_TopBackRight },
125 { QAudioFormat::TopSideLeft, kAudioChannelLabel_LeftTopMiddle },
126 { QAudioFormat::TopSideRight, kAudioChannelLabel_RightTopMiddle },
127 { QAudioFormat::TopBackCenter, kAudioChannelLabel_TopBackCenter },
129
130std::unique_ptr<AudioChannelLayout> CoreAudioUtils::toAudioChannelLayout(const QAudioFormat &format, UInt32 *size)
131{
132 auto channelConfig = format.channelConfig();
135
136 *size = sizeof(AudioChannelLayout) + int(QAudioFormat::NChannelPositions)*sizeof(AudioChannelDescription);
137 auto *layout = static_cast<AudioChannelLayout *>(malloc(*size));
138 memset(layout, 0, *size);
139 layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
140
141 for (const auto &m : channelMap) {
143 layout->mChannelDescriptions[layout->mNumberChannelDescriptions++].mChannelLabel = m.label;
144 }
145
147 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
148 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
149 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
150 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 0.f;
151 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
152 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
153 }
155 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
156 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
157 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
158 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = -45.f;
159 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
160 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
161 }
163 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
164 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
165 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
166 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 45.f;
167 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
168 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
169 }
170
171 return std::unique_ptr<AudioChannelLayout>(layout);
172}
173
174static constexpr struct {
175 AudioChannelLayoutTag tag;
177} layoutTagMap[] = {
178 { kAudioChannelLayoutTag_Mono, QAudioFormat::ChannelConfigMono },
179 { kAudioChannelLayoutTag_Stereo, QAudioFormat::ChannelConfigStereo },
180 { kAudioChannelLayoutTag_StereoHeadphones, QAudioFormat::ChannelConfigStereo },
181 { kAudioChannelLayoutTag_MPEG_1_0, QAudioFormat::ChannelConfigMono },
182 { kAudioChannelLayoutTag_MPEG_2_0, QAudioFormat::ChannelConfigStereo },
183 { kAudioChannelLayoutTag_MPEG_3_0_A, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
186 { kAudioChannelLayoutTag_MPEG_4_0_A, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
190 { kAudioChannelLayoutTag_MPEG_5_0_A, QAudioFormat::ChannelConfigSurround5Dot0 },
191 { kAudioChannelLayoutTag_MPEG_5_1_A, QAudioFormat::ChannelConfigSurround5Dot1 },
192 { kAudioChannelLayoutTag_MPEG_6_1_A, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
199 { kAudioChannelLayoutTag_MPEG_7_1_A, QAudioFormat::ChannelConfigSurround7Dot1 },
200 { kAudioChannelLayoutTag_SMPTE_DTV, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
208
209 { kAudioChannelLayoutTag_ITU_2_1, QAudioFormat::ChannelConfig2Dot1 },
210 { kAudioChannelLayoutTag_ITU_2_2, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
215
216
218{
219 for (const auto &m : layoutTagMap) {
220 if (m.tag == layout->mChannelLayoutTag)
221 return m.channelConfig;
222 }
223
224 quint32 channels = 0;
225 if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) {
226 // special case 1 and 2 channel configs, as they are often reported without proper descriptions
227 if (layout->mNumberChannelDescriptions == 1 && layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown)
229 if (layout->mNumberChannelDescriptions == 2 &&
230 layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown &&
231 layout->mChannelDescriptions[1].mChannelLabel == kAudioChannelLabel_Unknown)
233
234 for (uint i = 0; i < layout->mNumberChannelDescriptions; ++i) {
235 const auto channelLabel = layout->mChannelDescriptions[i].mChannelLabel;
236 if (channelLabel == kAudioChannelLabel_Unknown) {
237 // Any number of unknown channel labels occurs for loopback audio devices.
238 // E.g. the case is reproduced with installed software Soundflower.
239 continue;
240 }
241
242 const auto found = std::find_if(channelMap, std::end(channelMap),
243 [channelLabel](const auto &labelWithPos) {
244 return labelWithPos.label == channelLabel;
245 });
246
247 if (found == std::end(channelMap))
248 qWarning() << "audio device has unrecognized channel, index:" << i
249 << "label:" << channelLabel;
250 else
251 channels |= QAudioFormat::channelConfig(found->pos);
252 }
253 } else {
254 qWarning() << "Channel layout uses unimplemented format, channelLayoutTag:"
255 << layout->mChannelLayoutTag;
256 }
257 return QAudioFormat::ChannelConfig(channels);
258}
259
260// QAudioRingBuffer
262 m_bufferSize(bufferSize)
263{
264 m_buffer = new char[m_bufferSize];
265 reset();
266}
267
269{
270 delete[] m_buffer;
271}
272
274{
275 const int used = m_bufferUsed.fetchAndAddAcquire(0);
276
277 if (used > 0) {
278 const int readSize = qMin(size, qMin(m_bufferSize - m_readPos, used));
279
280 return readSize > 0 ? Region(m_buffer + m_readPos, readSize) : Region(0, 0);
281 }
282
283 return Region(0, 0);
284}
285
287{
288 m_readPos = (m_readPos + region.second) % m_bufferSize;
289
290 m_bufferUsed.fetchAndAddRelease(-region.second);
291}
292
294{
295 const int free = m_bufferSize - m_bufferUsed.fetchAndAddAcquire(0);
296
298
299 if (free > 0) {
300 const int writeSize = qMin(size, qMin(m_bufferSize - m_writePos, free));
301 output = writeSize > 0 ? Region(m_buffer + m_writePos, writeSize) : Region(0, 0);
302 } else {
303 output = Region(0, 0);
304 }
305#ifdef QT_DEBUG_COREAUDIO
306 qDebug("acquireWriteRegion(%d) free: %d returning Region(%p, %d)", size, free, output.first, output.second);
307#endif
308 return output;
309}
311{
312 m_writePos = (m_writePos + region.second) % m_bufferSize;
313
314 m_bufferUsed.fetchAndAddRelease(region.second);
315#ifdef QT_DEBUG_COREAUDIO
316 qDebug("releaseWriteRegion(%p,%d): m_writePos:%d", region.first, region.second, m_writePos);
317#endif
318}
319
321{
322 return m_bufferUsed.loadRelaxed();
323}
324
326{
327 return m_bufferSize - m_bufferUsed.loadRelaxed();
328}
329
331{
332 return m_bufferSize;
333}
334
336{
337 m_readPos = 0;
338 m_writePos = 0;
339 m_bufferUsed.storeRelaxed(0);
340}
341
void releaseWriteRegion(Region const &region)
Region acquireWriteRegion(int size)
Region acquireReadRegion(int size)
void releaseReadRegion(Region const &region)
QPair< char *, int > Region
CoreAudioRingBuffer(int bufferSize)
static Q_MULTIMEDIA_EXPORT QAudioFormat toQAudioFormat(const AudioStreamBasicDescription &streamFormat)
static AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const &audioFormat)
static Q_MULTIMEDIA_EXPORT std::unique_ptr< AudioChannelLayout > toAudioChannelLayout(const QAudioFormat &format, UInt32 *size)
static double frequency()
static QAudioFormat::ChannelConfig fromAudioChannelLayout(const AudioChannelLayout *layout)
static quint64 currentTime()
The QAudioFormat class stores audio stream parameter information.
constexpr void setSampleRate(int sampleRate) noexcept
Sets the sample rate to samplerate in Hertz.
constexpr int channelCount() const noexcept
Returns the current channel count value.
AudioChannelPosition
Describes the possible audio channel positions.
static Q_MULTIMEDIA_EXPORT ChannelConfig defaultChannelConfigForChannelCount(int channelCount)
Returns a default channel configuration for channelCount.
constexpr int bytesPerSample() const noexcept
Returns the number of bytes required to represent one sample in this format.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
constexpr SampleFormat sampleFormat() const noexcept
Returns the current sample format.
static constexpr int NChannelPositions
SampleFormat
Qt will always expect and use samples in the endianness of the host platform.
constexpr int bytesPerFrame() const
Returns the number of bytes required to represent one frame (a sample in each channel) in this format...
constexpr ChannelConfig channelConfig() const noexcept
Returns the current channel configuration.
constexpr void setSampleFormat(SampleFormat f) noexcept
Sets the sample format to format.
constexpr void setChannelCount(int channelCount) noexcept
Sets the channel count to channels.
ChannelConfig
\variable QAudioFormat::NChannelPositions
@ ChannelConfigSurround7Dot1
@ ChannelConfigSurround5Dot1
@ ChannelConfigSurround5Dot0
T fetchAndAddAcquire(T valueToAdd) noexcept
void storeRelaxed(T newValue) noexcept
T fetchAndAddRelease(T valueToAdd) noexcept
T loadRelaxed() const noexcept
@ ByteOrder
Definition qsysinfo.h:34
@ LittleEndian
Definition qsysinfo.h:30
Combined button and popup list for selecting options.
static constexpr struct @713 channelMap[]
static constexpr struct @714 layoutTagMap[]
QAudioFormat::ChannelConfig channelConfig
AudioChannelLayoutTag tag
static bool initialize()
Definition qctf.cpp:67
quint8 channelMap
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
const GLfloat * m
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLsizei const GLchar * label
[43]
GLint GLsizei GLsizei GLenum format
@ desc
unsigned int quint32
Definition qtypes.h:45
unsigned long long quint64
Definition qtypes.h:56
unsigned int uint
Definition qtypes.h:29
QT_BEGIN_NAMESPACE typedef uchar * output
QVBoxLayout * layout