Files
wiicompiled/runtime/tests/macos_external_audio_tests.cpp
T
theofficialgman 279ce8328f Macos support into main (#228)
* macos: add x86_64 Intel support

* (feat) Apple Silicon macOS CI building and Setup.pkg documentation (#118)

* docs(macos): document Setup.pkg installation

Add Apple Silicon macOS CI coverage for runtime configuration, substrate tests, and Setup.pkg packaging alongside the macOS installation instructions.

* macos: pin Apple Silicon deployment target

* fix(macos): restore Retro Rewind local builds

* macos: package universal setup tools

* ci: build macOS input expression tests

* runtime: Do not force 14.0 minimum anymore

new minimum is 12.0

* macos: support older libc++ algorithms

* macos: allow undefined MTLLogStateDescriptor for older SDKs

* tests: deflake input expression timing window test

* build: Fix mac tests

* translator: emit null statement after continuation labels for C++17 compatibility

* feat(macos): add MetalFX spatial upscaling

* Fix automatic music muting on macOS

Co-Authored-By: Michael G <10155689+DarthMDev@users.noreply.github.com>
Co-Authored-By: Daan Vervacke <23398694+DaanVervacke@users.noreply.github.com>

* Address CodeRabbit review comments and integrate upstream TLS cmake

* Remove version requirement for running package CI

Co-Authored-By: Michael G <10155689+DarthMDev@users.noreply.github.com>

* publish-app: fix dependency discovery from build dir, handle spaces in paths, and fail on missing deps

* publish-app: preserve subdirectory suffix during @rpath dependency lookup

* cmake: synchronize CMAKE_SYSTEM_PROCESSOR with CMAKE_OSX_ARCHITECTURES on macOS

* fix rpaths

---------

Co-authored-by: DarthM <mgracer48@yahoo.com>
Co-authored-by: Michael G <10155689+DarthMDev@users.noreply.github.com>
Co-authored-by: Daan Vervacke <23398694+DaanVervacke@users.noreply.github.com>
Co-authored-by: patchzyy <64382339+patchzyy@users.noreply.github.com>
2026-10-03 11:07:15 +02:00

110 lines
4.2 KiB
C++

#include "external_audio_macos.h"
#include <CoreAudio/CoreAudio.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <vector>
namespace {
struct Process {
pid_t pid;
UInt32 output;
bool disappeared = false;
};
std::vector<Process> processes;
bool supported = true;
bool queryFailed = false;
int sizeRaces = 0;
void Require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
void Expect(bool available, bool playing, const char* message) {
const auto status = MusicAttenuation::QueryMacOSExternalAudio();
Require(status.available == available && status.playing == playing, message);
}
} // namespace
// Fake only the Core Audio boundary so the production enumeration and process
// filtering run unchanged, without depending on other apps on the test machine.
// Use the public selector ABI values so these mocks also build with older SDKs.
extern "C" Boolean AudioObjectHasProperty(AudioObjectID object,
const AudioObjectPropertyAddress* address) {
Require(object == kAudioObjectSystemObject &&
address->mSelector == 'prs#',
"Must query the system process list");
return supported;
}
extern "C" OSStatus AudioObjectGetPropertyDataSize(
AudioObjectID, const AudioObjectPropertyAddress*, UInt32, const void*, UInt32* size) {
if (queryFailed) return kAudioHardwareUnspecifiedError;
*size = static_cast<UInt32>(processes.size() * sizeof(AudioObjectID));
return noErr;
}
extern "C" OSStatus AudioObjectGetPropertyData(
AudioObjectID object, const AudioObjectPropertyAddress* address, UInt32,
const void*, UInt32* size, void* data) {
Require(address->mScope == kAudioObjectPropertyScopeGlobal, "Must use global scope");
if (object == kAudioObjectSystemObject) {
if (sizeRaces > 0) {
--sizeRaces;
return kAudioHardwareBadPropertySizeError;
}
for (size_t i = 0; i < processes.size(); ++i) {
static_cast<AudioObjectID*>(data)[i] = static_cast<AudioObjectID>(i + 100);
}
*size = static_cast<UInt32>(processes.size() * sizeof(AudioObjectID));
return noErr;
}
const auto& process = processes.at(object - 100);
if (process.disappeared) return kAudioHardwareBadObjectError;
if (address->mSelector == 'ppid') {
std::memcpy(data, &process.pid, sizeof(process.pid));
*size = sizeof(process.pid);
} else {
Require(address->mSelector == 'piro',
"Input-only activity must not count as playback");
Require(process.pid != getpid(), "Must exclude the game's own output");
std::memcpy(data, &process.output, sizeof(process.output));
*size = sizeof(process.output);
}
return noErr;
}
int main() {
supported = false;
Expect(false, false, "Older macOS must report unavailable");
supported = true;
Expect(true, false, "An empty process list is available but inactive");
processes = {{getpid(), 1}};
Expect(true, false, "Game audio alone must not trigger muting");
processes.push_back({getpid() + 1, 0});
Expect(true, false, "An idle or input-only app must not trigger muting");
processes.back().output = 1;
Expect(true, true, "External playback must trigger muting");
processes.back().output = 0;
Expect(true, false, "Stopping playback must restore music");
processes.back().output = 1;
processes.back().disappeared = true;
Expect(true, false, "Exiting apps must not leave music muted");
processes.push_back({getpid() + 2, 1});
Expect(true, true, "A disappearing app must not hide another active player");
sizeRaces = 1;
Expect(true, true, "A growing process list must be retried");
sizeRaces = 3;
Expect(false, false, "List retries must be bounded");
Expect(true, true, "Later polls must recover from list races");
queryFailed = true;
Expect(false, false, "Query failure must clear playing state");
queryFailed = false;
Expect(true, true, "Monitoring must recover from query failure");
processes = {{0, 1}};
Expect(true, false, "Unknown PIDs must not count as external playback");
std::cout << "macOS external audio tests passed\n";
}