Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qcoreaudioutils_p.h"
#include <QtCore/qdebug.h>
#include <QtMultimedia/private/qmultimedia_ranges_p.h>
#include <QtMultimedia/private/qapple_utils_p.h>
#ifdef Q_OS_MACOS
# include <CoreAudio/AudioHardware.h>
# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
# include <QtMultimedia/private/qaudioformat_p.h>
#endif
QT_BEGIN_NAMESPACE
namespace QCoreAudioUtils {
namespace ranges = QtMultimediaPrivate::ranges;
QAudioFormat toPreferredQAudioFormat(AudioStreamBasicDescription const &sf)
{
// coreaudio will do the format conversions for us, we only need to give the best match
const QAudioFormat::SampleFormat format = [&] {
const bool isFloat = sf.mFormatFlags & kAudioFormatFlagIsFloat;
switch (sf.mBitsPerChannel) {
case 8:
return QAudioFormat::UInt8;
case 16:
return QAudioFormat::Int16;
case 32:
return isFloat ? QAudioFormat::Float : QAudioFormat::Int32;
default:
return QAudioFormat::Float;
}
}();
QAudioFormat audioFormat;
audioFormat.setSampleFormat(format);
audioFormat.setSampleRate(sf.mSampleRate);
audioFormat.setChannelCount(sf.mChannelsPerFrame);
return audioFormat;
}
AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const& audioFormat)
{
AudioStreamBasicDescription sf;
sf.mFormatFlags = kAudioFormatFlagIsPacked;
sf.mSampleRate = audioFormat.sampleRate();
sf.mFramesPerPacket = 1;
sf.mChannelsPerFrame = audioFormat.channelCount();
sf.mBitsPerChannel = audioFormat.bytesPerSample() * 8;
sf.mBytesPerFrame = audioFormat.bytesPerFrame();
sf.mBytesPerPacket = sf.mFramesPerPacket * sf.mBytesPerFrame;
sf.mFormatID = kAudioFormatLinearPCM;
switch (audioFormat.sampleFormat()) {
case QAudioFormat::Int16:
case QAudioFormat::Int32:
sf.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
break;
case QAudioFormat::Float:
sf.mFormatFlags |= kAudioFormatFlagIsFloat;
break;
case QAudioFormat::UInt8:
/* default */
case QAudioFormat::Unknown:
case QAudioFormat::NSampleFormats:
break;
}
return sf;
}
static constexpr struct {
QAudioFormat::AudioChannelPosition pos;
AudioChannelLabel label;
} channelMap[] = {
{ QAudioFormat::FrontLeft, kAudioChannelLabel_Left },
{ QAudioFormat::FrontRight, kAudioChannelLabel_Right },
{ QAudioFormat::FrontCenter, kAudioChannelLabel_Center },
{ QAudioFormat::LFE, kAudioChannelLabel_LFEScreen },
{ QAudioFormat::BackLeft, kAudioChannelLabel_LeftSurround },
{ QAudioFormat::BackRight, kAudioChannelLabel_RightSurround },
{ QAudioFormat::FrontLeftOfCenter, kAudioChannelLabel_LeftCenter },
{ QAudioFormat::FrontRightOfCenter, kAudioChannelLabel_RightCenter },
{ QAudioFormat::BackCenter, kAudioChannelLabel_CenterSurround },
{ QAudioFormat::LFE2, kAudioChannelLabel_LFE2 },
{ QAudioFormat::SideLeft, kAudioChannelLabel_LeftSurroundDirect }, // ???
{ QAudioFormat::SideRight, kAudioChannelLabel_RightSurroundDirect }, // ???
{ QAudioFormat::TopFrontLeft, kAudioChannelLabel_VerticalHeightLeft },
{ QAudioFormat::TopFrontRight, kAudioChannelLabel_VerticalHeightRight },
{ QAudioFormat::TopFrontCenter, kAudioChannelLabel_VerticalHeightCenter },
{ QAudioFormat::TopCenter, kAudioChannelLabel_CenterTopMiddle },
{ QAudioFormat::TopBackLeft, kAudioChannelLabel_TopBackLeft },
{ QAudioFormat::TopBackRight, kAudioChannelLabel_TopBackRight },
{ QAudioFormat::TopSideLeft, kAudioChannelLabel_LeftTopMiddle },
{ QAudioFormat::TopSideRight, kAudioChannelLabel_RightTopMiddle },
{ QAudioFormat::TopBackCenter, kAudioChannelLabel_TopBackCenter },
};
std::unique_ptr<AudioChannelLayout, QFreeDeleter>
toAudioChannelLayout(const QAudioFormat &format, UInt32 *size)
{
auto channelConfig = format.channelConfig();
if (channelConfig == QAudioFormat::ChannelConfigUnknown)
channelConfig = QAudioFormat::defaultChannelConfigForChannelCount(format.channelCount());
*size = sizeof(AudioChannelLayout) + int(QAudioFormat::NChannelPositions)*sizeof(AudioChannelDescription);
auto *layout = static_cast<AudioChannelLayout *>(malloc(*size));
memset(layout, 0, *size);
layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
for (const auto &m : channelMap) {
if (channelConfig & QAudioFormat::channelConfig(m.pos))
layout->mChannelDescriptions[layout->mNumberChannelDescriptions++].mChannelLabel = m.label;
}
if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontCenter)) {
auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 0.f;
desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
}
if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontLeft)) {
auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = -45.f;
desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
}
if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontRight)) {
auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 45.f;
desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
}
return std::unique_ptr<AudioChannelLayout, QFreeDeleter>(layout);
}
static constexpr struct {
AudioChannelLayoutTag tag;
QAudioFormat::ChannelConfig channelConfig;
} layoutTagMap[] = {
{ kAudioChannelLayoutTag_Mono, QAudioFormat::ChannelConfigMono },
{ kAudioChannelLayoutTag_Stereo, QAudioFormat::ChannelConfigStereo },
{ kAudioChannelLayoutTag_StereoHeadphones, QAudioFormat::ChannelConfigStereo },
{ kAudioChannelLayoutTag_MPEG_1_0, QAudioFormat::ChannelConfigMono },
{ kAudioChannelLayoutTag_MPEG_2_0, QAudioFormat::ChannelConfigStereo },
{ kAudioChannelLayoutTag_MPEG_3_0_A, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
QAudioFormat::FrontRight,
QAudioFormat::FrontCenter) },
{ kAudioChannelLayoutTag_MPEG_4_0_A, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
QAudioFormat::FrontRight,
QAudioFormat::FrontCenter,
QAudioFormat::BackCenter) },
{ kAudioChannelLayoutTag_MPEG_5_0_A, QAudioFormat::ChannelConfigSurround5Dot0 },
{ kAudioChannelLayoutTag_MPEG_5_1_A, QAudioFormat::ChannelConfigSurround5Dot1 },
{ kAudioChannelLayoutTag_MPEG_6_1_A, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
QAudioFormat::FrontRight,
QAudioFormat::FrontCenter,
QAudioFormat::LFE,
QAudioFormat::BackLeft,
QAudioFormat::BackRight,
QAudioFormat::BackCenter) },
{ kAudioChannelLayoutTag_MPEG_7_1_A, QAudioFormat::ChannelConfigSurround7Dot1 },
{ kAudioChannelLayoutTag_SMPTE_DTV, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
QAudioFormat::FrontRight,
QAudioFormat::FrontCenter,
QAudioFormat::LFE,
QAudioFormat::BackLeft,
QAudioFormat::BackRight,
QAudioFormat::TopFrontLeft,
QAudioFormat::TopFrontRight) },
{ kAudioChannelLayoutTag_ITU_2_1, QAudioFormat::ChannelConfig2Dot1 },
{ kAudioChannelLayoutTag_ITU_2_2, QAudioFormat::channelConfig(QAudioFormat::FrontLeft,
QAudioFormat::FrontRight,
QAudioFormat::BackLeft,
QAudioFormat::BackRight) }
};
QAudioFormat::ChannelConfig fromAudioChannelLayout(const AudioChannelLayout *layout)
{
for (const auto &m : layoutTagMap) {
if (m.tag == layout->mChannelLayoutTag)
return m.channelConfig;
}
quint32 channels = 0;
if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) {
// special case 1 and 2 channel configs, as they are often reported without proper descriptions
if (layout->mNumberChannelDescriptions == 1
&& (layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown
|| layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Mono))
return QAudioFormat::ChannelConfigMono;
if (layout->mNumberChannelDescriptions == 2 &&
layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown &&
layout->mChannelDescriptions[1].mChannelLabel == kAudioChannelLabel_Unknown)
return QAudioFormat::ChannelConfigStereo;
for (uint i = 0; i < layout->mNumberChannelDescriptions; ++i) {
const auto channelLabel = layout->mChannelDescriptions[i].mChannelLabel;
if (channelLabel == kAudioChannelLabel_Unknown) {
// Any number of unknown channel labels occurs for loopback audio devices.
// E.g. the case is reproduced with installed software Soundflower.
continue;
}
const auto found = ranges::find_if(channelMap, [&](const auto &labelWithPos) {
return labelWithPos.label == channelLabel;
});
if (found == std::end(channelMap))
qWarning() << "audio device has unrecognized channel, index:" << i
<< "label:" << channelLabel;
else
channels |= QAudioFormat::channelConfig(found->pos);
}
} else {
qWarning() << "Channel layout uses unimplemented format, channelLayoutTag:"
<< layout->mChannelLayoutTag;
}
return QAudioFormat::ChannelConfig(channels);
}
std::optional<AudioUnitHandle> makeAudioUnitForIO()
{
AudioComponentDescription componentDescription;
componentDescription.componentType = kAudioUnitType_Output;
#if defined(Q_OS_MACOS)
componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
#else
componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
componentDescription.componentFlags = 0;
componentDescription.componentFlagsMask = 0;
AudioComponent component = AudioComponentFindNext(nullptr, &componentDescription);
if (component == nullptr) {
qWarning() << "makeAudioUnitForIO: Failed to find Output component";
return std::nullopt;
}
AudioUnitHandle audioUnit;
if (AudioComponentInstanceNew(component, &audioUnit) != noErr) {
qWarning() << "makeAudioUnitForIO: Unable to Open Output Component";
return std::nullopt;
}
return audioUnit;
}
bool audioUnitSetInputEnabled(AudioUnitHandle &audioUnit, bool enabled)
{
UInt32 enable = enabled ? 1 : 0;
if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, 1, &enable, sizeof(enable))
!= noErr) {
qWarning() << "AudioUnit: Unable to enable input";
return false;
}
return true;
}
bool audioUnitSetOutputEnabled(AudioUnitHandle &audioUnit, bool enabled)
{
UInt32 enable = enabled ? 1 : 0;
if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, 0, &enable, sizeof(enable))
!= noErr) {
qWarning() << "AudioUnit: Unable to enable output";
return false;
}
return true;
}
#ifdef Q_OS_MACOS
bool audioUnitSetCurrentDevice(AudioUnitHandle &audioUnit, AudioObjectID deviceID)
{
if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &deviceID, sizeof(deviceID))
!= noErr) {
qWarning() << "AudioUnit: Unable to use set device ID";
return false;
}
return true;
}
#endif
bool audioUnitSetInputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
const AudioStreamBasicDescription &format)
{
OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, element, &format, sizeof(format));
if (status != noErr) {
qWarning() << "AudioUnit: Unable to set stream format" << QtMultimediaPrivate::QOSStatus(status);
return false;
}
return true;
}
bool audioUnitSetOutputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
const AudioStreamBasicDescription &format)
{
if (AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, element, &format, sizeof(format))
!= noErr) {
qWarning() << "AudioUnit: Unable to set stream format";
return false;
}
return true;
}
#ifdef Q_OS_MACOS
std::optional<int> audioUnitGetFramesPerBuffer(AudioUnitHandle &audioUnit)
{
int numberOfFrames;
UInt32 size = sizeof(UInt32);
if (AudioUnitGetProperty(audioUnit.get(), kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global, 0, &numberOfFrames, &size)
== noErr) {
return numberOfFrames;
}
qWarning() << "audioUnitGetFramesPerBuffer: Failed to get audio period size";
AudioValueRange bufferRange;
size = sizeof(AudioValueRange);
if (AudioUnitGetProperty(audioUnit.get(), kAudioDevicePropertyBufferFrameSizeRange,
kAudioUnitScope_Global, 0, &bufferRange, &size)
== noErr) {
return int(bufferRange.mMaximum);
}
return std::nullopt;
}
bool audioObjectSetSamplingRate(AudioObjectID id, int samplingRate)
{
constexpr AudioObjectPropertyAddress sampleRateAddress = {
kAudioDevicePropertyNominalSampleRate,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain,
};
Float64 sampleRateArg(samplingRate);
OSStatus status = AudioObjectSetPropertyData(id, &sampleRateAddress, 0, nullptr,
sizeof(Float64), &sampleRateArg);
if (status != noErr) {
qDebug() << "AudioObjectSetPropertyData failed" << QtMultimediaPrivate::QOSStatus(status);
return false;
}
return true;
}
std::optional<int> audioObjectGetSamplingRate(AudioObjectID id)
{
constexpr AudioObjectPropertyAddress sampleRateAddress = {
kAudioDevicePropertyNominalSampleRate,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain,
};
Float64 rate;
uint32_t size = sizeof(Float64);
OSStatus status = AudioObjectGetPropertyData(id, &sampleRateAddress, 0, nullptr, &size, &rate);
if (status != noErr) {
qDebug() << "AudioObjectGetPropertyData failed" << status;
return {};
}
return int(rate);
}
std::optional<int> audioObjectFindBestNominalSampleRate(AudioObjectID id, QAudioDevice::Mode mode,
int rate)
{
auto optionalAvailableRates = getAudioPropertyList<Float64>(
id, makePropertyAddress(kAudioDevicePropertyAvailableNominalSampleRates, mode));
if (!optionalAvailableRates)
return std::nullopt;
using namespace QtMultimediaPrivate;
return findClosestSamplingRate(rate,
QSpan<const double>{
*optionalAvailableRates,
});
}
bool audioObjectSetFramesPerBuffer(AudioObjectID id, int32_t bufferFrames)
{
constexpr AudioObjectPropertyAddress bufferFrameSizeAddress = {
kAudioDevicePropertyBufferFrameSize,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain,
};
OSStatus status = AudioObjectSetPropertyData(id, &bufferFrameSizeAddress, 0, nullptr,
sizeof(int32_t), &bufferFrames);
if (status != noErr) {
qDebug() << "AudioObjectSetPropertyData failed" << QtMultimediaPrivate::QOSStatus(status);
return false;
}
return true;
}
#endif
bool audioUnitIsRunning(AudioUnitHandle &audioUnit)
{
UInt32 isRunning = 0;
UInt32 size = sizeof(isRunning);
OSStatus status = AudioUnitGetProperty(audioUnit.get(), kAudioOutputUnitProperty_IsRunning,
kAudioUnitScope_Global, 0, &isRunning, &size);
if (status != noErr) {
qDebug() << "AudioUnitGetProperty failed" << QtMultimediaPrivate::QOSStatus(status);
return false;
}
return bool(isRunning);
}
bool audioUnitSetRenderCallback(AudioUnitHandle &audioUnit, AURenderCallbackStruct &callback)
{
OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global, 0, &callback, sizeof(callback));
if (status != noErr) {
qWarning() << "AudioUnitSetProperty: Failed to set AudioUnit "
"kAudioUnitProperty_SetRenderCallback"
<< QtMultimediaPrivate::QOSStatus(status);
return false;
}
return true;
}
std::optional<int> audioUnitGetFramesPerSlice(AudioUnitHandle &audioUnit)
{
int numberOfFrames;
UInt32 size = sizeof(UInt32);
if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_MaximumFramesPerSlice,
kAudioUnitScope_Global, 0, &numberOfFrames, &size)
== noErr) {
return numberOfFrames;
}
return std::nullopt;
}
std::optional<AudioStreamBasicDescription> audioUnitGetInputStreamFormat(AudioUnitHandle &audioUnit,
AudioUnitElement element)
{
AudioStreamBasicDescription ret;
UInt32 size = sizeof(AudioStreamBasicDescription);
if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, element, &ret, &size)
!= noErr) {
qWarning() << "QAudioSource: Unable to retrieve device format";
return std::nullopt;
}
return ret;
}
AudioUnitHandle::~AudioUnitHandle()
{
deinitialize();
}
bool AudioUnitHandle::initialize()
{
Q_ASSERT(!m_initialized);
if (AudioUnitInitialize(get())) {
qWarning() << "AudioUnitHandle: Failed to initialize AudioUnit";
return false;
}
m_initialized = true;
return true;
}
void AudioUnitHandle::deinitialize()
{
if (m_initialized)
AudioUnitUninitialize(get());
m_initialized = false;
}
} // namespace QCoreAudioUtils
QT_END_NAMESPACE