Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
228 && (layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown
229 || layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Mono))
231 if (layout->mNumberChannelDescriptions == 2 &&
232 layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown &&
233 layout->mChannelDescriptions[1].mChannelLabel == kAudioChannelLabel_Unknown)
235
236 for (uint i = 0; i < layout->mNumberChannelDescriptions; ++i) {
237 const auto channelLabel = layout->mChannelDescriptions[i].mChannelLabel;
238 if (channelLabel == kAudioChannelLabel_Unknown) {
239 // Any number of unknown channel labels occurs for loopback audio devices.
240 // E.g. the case is reproduced with installed software Soundflower.
241 continue;
242 }
243
244 const auto found = std::find_if(channelMap, std::end(channelMap),
245 [channelLabel](const auto &labelWithPos) {
246 return labelWithPos.label == channelLabel;
247 });
248
249 if (found == std::end(channelMap))
250 qWarning() << "audio device has unrecognized channel, index:" << i
251 << "label:" << channelLabel;
252 else
253 channels |= QAudioFormat::channelConfig(found->pos);
254 }
255 } else {
256 qWarning() << "Channel layout uses unimplemented format, channelLayoutTag:"
257 << layout->mChannelLayoutTag;
258 }
259 return QAudioFormat::ChannelConfig(channels);
260}
261
262// QAudioRingBuffer
264 m_bufferSize(bufferSize)
265{
266 m_buffer = new char[m_bufferSize];
267 reset();
268}
269
271{
272 delete[] m_buffer;
273}
274
276{
277 const int used = m_bufferUsed.fetchAndAddAcquire(0);
278
279 if (used > 0) {
280 const int readSize = qMin(size, qMin(m_bufferSize - m_readPos, used));
281
282 return readSize > 0 ? Region(m_buffer + m_readPos, readSize) : Region(0, 0);
283 }
284
285 return Region(0, 0);
286}
287
289{
290 m_readPos = (m_readPos + region.second) % m_bufferSize;
291
292 m_bufferUsed.fetchAndAddRelease(-region.second);
293}
294
296{
297 const int free = m_bufferSize - m_bufferUsed.fetchAndAddAcquire(0);
298
300
301 if (free > 0) {
302 const int writeSize = qMin(size, qMin(m_bufferSize - m_writePos, free));
303 output = writeSize > 0 ? Region(m_buffer + m_writePos, writeSize) : Region(0, 0);
304 } else {
305 output = Region(0, 0);
306 }
307#ifdef QT_DEBUG_COREAUDIO
308 qDebug("acquireWriteRegion(%d) free: %d returning Region(%p, %d)", size, free, output.first, output.second);
309#endif
310 return output;
311}
313{
314 m_writePos = (m_writePos + region.second) % m_bufferSize;
315
316 m_bufferUsed.fetchAndAddRelease(region.second);
317#ifdef QT_DEBUG_COREAUDIO
318 qDebug("releaseWriteRegion(%p,%d): m_writePos:%d", region.first, region.second, m_writePos);
319#endif
320}
321
323{
324 return m_bufferUsed.loadRelaxed();
325}
326
328{
329 return m_bufferSize - m_bufferUsed.loadRelaxed();
330}
331
333{
334 return m_bufferSize;
335}
336
338{
339 m_readPos = 0;
340 m_writePos = 0;
341 m_bufferUsed.storeRelaxed(0);
342}
343
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 @722 layoutTagMap[]
static constexpr struct @721 channelMap[]
QAudioFormat::ChannelConfig channelConfig
AudioChannelLayoutTag tag
static bool initialize()
Definition qctf.cpp:94
quint8 channelMap
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
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
unsigned int quint32
Definition qtypes.h:50
unsigned long long quint64
Definition qtypes.h:61
unsigned int uint
Definition qtypes.h:34
QT_BEGIN_NAMESPACE typedef uchar * output
QVBoxLayout * layout