init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "base/timer.h"
#include "base/weak_ptr.h"
#include "webrtc/platform/webrtc_platform_environment.h"
#include <media/engine/webrtc_media_engine.h>
namespace webrtc {
template <class T>
class scoped_refptr;
} // namespace webrtc
namespace rtc {
template <typename T>
using scoped_refptr = webrtc::scoped_refptr<T>;
} // namespace rtc
namespace webrtc {
class TaskQueueFactory;
class AudioDeviceModule;
} // namespace webrtc
namespace Webrtc::Platform {
class EnvironmentMac final : public Environment, public base::has_weak_ptr {
public:
explicit EnvironmentMac(not_null<EnvironmentDelegate*> delegate);
~EnvironmentMac();
QString defaultId(DeviceType type) override;
DeviceInfo device(DeviceType type, const QString &id) override;
std::vector<DeviceInfo> devices(DeviceType type) override;
bool refreshFullListOnChange(DeviceType type) override;
bool desktopCaptureAllowed() const override;
std::optional<QString> uniqueDesktopCaptureSource() const override;
void defaultIdRequested(DeviceType type) override;
void devicesRequested(DeviceType type) override;
void setCaptureMuted(bool muted) override;
void setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track) override;
void defaultPlaybackDeviceChanged();
void defaultCaptureDeviceChanged();
void audioDeviceListChanged();
[[nodiscard]] static QString DefaultId(DeviceType type);
private:
void captureMuteSubscribe();
void captureMuteUnsubscribe();
void captureMuteRestartAdm();
void sendCaptureMutedValue();
const not_null<EnvironmentDelegate*> _delegate;
base::Timer _captureMuteDebounceTimer;
CaptureMuteTracker *_captureMuteTracker = nullptr;
bool _captureMuteNotification = false;
bool _captureMuted = false;
std::unique_ptr<webrtc::TaskQueueFactory> _admTaskQueueFactory;
rtc::scoped_refptr<webrtc::AudioDeviceModule> _adm;
Fn<void(DeviceResolvedId)> _admSetDeviceIdCallback;
DeviceResolvedId _admCaptureDeviceId;
rpl::lifetime _captureMuteTrackerLifetime;
rpl::lifetime _captureMuteSubscriptionLifetime;
rpl::lifetime _lifetime;
};
} // namespace Webrtc::Platform

View File

@@ -0,0 +1,691 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "webrtc/platform/mac/webrtc_environment_mac.h"
#include "base/weak_ptr.h"
#include "webrtc/webrtc_environment.h"
#include "webrtc/webrtc_create_adm.h"
#include <modules/audio_device/include/audio_device_defines.h>
#include <modules/audio_device/include/audio_device.h>
#include <api/task_queue/default_task_queue_factory.h>
#import <AVFoundation/AVFoundation.h>
#import <IOKit/hidsystem/IOHIDLib.h>
#import <CoreAudio/CoreAudio.h>
#import <Cocoa/Cocoa.h>
@interface InputMuteObserver : NSObject {
}
- (id) init;
- (void) inputMuteStateChange:(NSNotification *)aNotification;
@end // @interface InputMuteObserver
@implementation InputMuteObserver {
}
- (id) init {
if (self = [super init]) {
}
return self;
}
- (void) inputMuteStateChange:(NSNotification *)aNotification {
}
@end // @implementation InputMuteObserver
namespace Webrtc::Platform {
namespace {
constexpr auto kMaxNameLength = 256;
constexpr auto kCaptureMuteDebounceTimeout = crl::time(300);
class EmptyCallback final : public webrtc::AudioTransport {
public:
int32_t RecordedDataIsAvailable(
const void* audioSamples,
const size_t nSamples,
const size_t nBytesPerSample,
const size_t nChannels,
const uint32_t samplesPerSec,
const uint32_t totalDelayMS,
const int32_t clockDrift,
const uint32_t currentMicLevel,
const bool keyPressed,
uint32_t& newMicLevel) override {
return 0;
}
// Implementation has to setup safe values for all specified out parameters.
int32_t NeedMorePlayData(
const size_t nSamples,
const size_t nBytesPerSample,
const size_t nChannels,
const uint32_t samplesPerSec,
void* audioSamples,
size_t& nSamplesOut, // NOLINT
int64_t* elapsed_time_ms,
int64_t* ntp_time_ms) override {
nSamplesOut = 0;
return 0;
}
void PullRenderData(
int bits_per_sample,
int sample_rate,
size_t number_of_channels,
size_t number_of_frames,
void* audio_data,
int64_t* elapsed_time_ms,
int64_t* ntp_time_ms) override {
}
};
class PropertyMonitor {
public:
using Method = void (EnvironmentMac::*)();
PropertyMonitor(
AudioObjectPropertyAddress address,
Method method);
void registerEnvironment(not_null<EnvironmentMac*> environment);
void unregisterEnvironment(not_null<EnvironmentMac*> environment);
private:
void subscribe();
void unsubscribe();
void process();
static OSStatus Callback(
AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress *inAddresses,
void *inClientData);
const AudioObjectPropertyAddress _address = {};
Method _method = nullptr;
std::vector<not_null<EnvironmentMac*>> _list;
};
auto DefaultPlaybackDeviceChangedMonitor = PropertyMonitor({
.mSelector = kAudioHardwarePropertyDefaultOutputDevice,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain,
}, &EnvironmentMac::defaultPlaybackDeviceChanged);
auto DefaultCaptureDeviceChangedMonitor = PropertyMonitor({
.mSelector = kAudioHardwarePropertyDefaultInputDevice,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain,
}, &EnvironmentMac::defaultCaptureDeviceChanged);
auto AudioDeviceListChangedMonitor = PropertyMonitor({
.mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain,
}, &EnvironmentMac::audioDeviceListChanged);
PropertyMonitor::PropertyMonitor(
AudioObjectPropertyAddress address,
Method method)
: _address(address)
, _method(method) {
}
void PropertyMonitor::registerEnvironment(
not_null<EnvironmentMac*> environment) {
if (empty(_list)) {
subscribe();
}
_list.push_back(environment);
}
void PropertyMonitor::unregisterEnvironment(
not_null<EnvironmentMac*> environment) {
_list.erase(ranges::remove(_list, environment), end(_list));
if (empty(_list)) {
unsubscribe();
}
}
void PropertyMonitor::subscribe() {
AudioObjectAddPropertyListener(
kAudioObjectSystemObject,
&_address,
&PropertyMonitor::Callback,
this);
}
void PropertyMonitor::unsubscribe() {
AudioObjectRemovePropertyListener(
kAudioObjectSystemObject,
&_address,
&PropertyMonitor::Callback,
this);
}
void PropertyMonitor::process() {
for (auto i = 0; i < _list.size(); ++i) {
(_list[i]->*_method)();
}
}
OSStatus PropertyMonitor::Callback(
AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress *inAddresses,
void *inClientData) {
const auto monitor = static_cast<PropertyMonitor*>(inClientData);
crl::on_main([monitor] {
monitor->process();
});
return 0;
}
[[nodiscard]] QString CFStringToQString(CFStringRef text) {
if (!text) {
return QString();
}
const auto length = CFStringGetLength(text);
const auto bytes = length * sizeof(QChar);
auto result = QString(length, QChar(0));
auto used = CFIndex(0);
const auto converted = CFStringGetBytes(
text,
CFRange{ 0, length },
kCFStringEncodingUTF16LE,
UInt8(),
false,
reinterpret_cast<UInt8*>(result.data()),
bytes,
&used);
if (!converted || !used || (used % sizeof(QChar))) {
return QString();
} else if (used < bytes) {
result.resize(used / sizeof(QChar));
}
return result;
}
[[nodiscard]] QString GetDeviceUID(AudioDeviceID deviceId) {
if (deviceId == kAudioDeviceUnknown) {
return QString();
}
auto uid = CFStringRef(nullptr);
const auto guard = gsl::finally([&] {
if (uid) {
CFRelease(uid);
}
});
auto size = UInt32(sizeof(uid));
const auto address = AudioObjectPropertyAddress{
.mSelector = kAudioDevicePropertyDeviceUID,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain,
};
const auto result = AudioObjectGetPropertyData(
deviceId,
&address,
0,
nil,
&size,
&uid);
if (result != kAudioHardwareNoError || !uid) {
return QString();
}
return CFStringToQString(uid);
}
[[nodiscard]] AudioDeviceID GetDeviceByUID(const QString &id) {
const auto utf = id.toUtf8();
auto uid = CFStringCreateWithCString(
nullptr,
utf.data(),
kCFStringEncodingUTF8);
if (!uid) {
return kAudioObjectUnknown;
}
const auto guard = gsl::finally([&] {
CFRelease(uid);
});
const auto address = AudioObjectPropertyAddress{
.mSelector = kAudioHardwarePropertyDeviceForUID,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain,
};
auto deviceId = AudioDeviceID(kAudioObjectUnknown);
auto deviceSize = UInt32(sizeof(deviceId));
AudioValueTranslation value;
value.mInputData = &uid;
value.mInputDataSize = sizeof(CFStringRef);
value.mOutputData = &deviceId;
value.mOutputDataSize = deviceSize;
auto valueSize = UInt32(sizeof(AudioValueTranslation));
const auto result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
0,
&valueSize,
&value);
return (result == noErr) ? deviceId : kAudioObjectUnknown;
}
[[nodiscard]] QString GetDeviceName(
DeviceType type,
AudioDeviceID deviceId) {
auto name = (CFStringRef)nullptr;
const auto guard = gsl::finally([&] {
if (name) {
CFRelease(name);
}
});
auto size = UInt32(sizeof(name));
const auto address = AudioObjectPropertyAddress{
.mSelector = kAudioDevicePropertyDeviceNameCFString,
.mScope = (type == DeviceType::Playback
? kAudioObjectPropertyScopeOutput
: kAudioObjectPropertyScopeInput),
.mElement = kAudioObjectPropertyElementMain,
};
const auto result = AudioObjectGetPropertyData(
deviceId,
&address,
0,
nullptr,
&size,
&name);
if (result != kAudioHardwareNoError || !name) {
return QString();
}
return CFStringToQString(name);
}
[[nodiscard]] bool DeviceHasType(
AudioDeviceID deviceId,
DeviceType type) {
auto size = UInt32(0);
const auto address = AudioObjectPropertyAddress{
.mSelector = kAudioDevicePropertyStreamConfiguration,
.mScope = (type == DeviceType::Playback
? kAudioObjectPropertyScopeOutput
: kAudioObjectPropertyScopeInput),
.mElement = kAudioObjectPropertyElementMain,
};
auto result = AudioObjectGetPropertyDataSize(
deviceId,
&address,
0,
0,
&size);
if (result != noErr) {
return false;
}
AudioBufferList *list = (AudioBufferList*)malloc(size);
const auto guard = gsl::finally([&] {
free(list);
});
result = AudioObjectGetPropertyData(
deviceId,
&address,
0,
nil,
&size,
list);
return (result == noErr) && (list->mNumberBuffers > 0);
}
[[nodiscard]] std::vector<AudioDeviceID> GetAllDeviceIds() {
auto size = UInt32();
const auto address = AudioObjectPropertyAddress{
.mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain,
};
auto result = AudioObjectGetPropertyDataSize(
kAudioObjectSystemObject,
&address,
0,
nil,
&size);
if (result != noErr) {
return {};
}
const auto count = size / sizeof(AudioDeviceID);
auto list = std::vector<AudioDeviceID>(count, kAudioDeviceUnknown);
result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
nil,
&size,
list.data());
return (result == noErr) ? list : std::vector<AudioDeviceID>();
}
[[nodiscard]] QString NS2QString(NSString *text) {
return QString::fromUtf8(
[text cStringUsingEncoding:NSUTF8StringEncoding]);
}
[[nodiscard]] QString CaptureDeviceId(AVCaptureDevice *device) {
return device ? NS2QString([device uniqueID]) : QString();
}
[[nodiscard]] DeviceInfo CaptureDeviceInfo(AVCaptureDevice *device) {
const auto id = CaptureDeviceId(device);
if (id.isEmpty()) {
return {};
}
return {
.id = id,
.name = NS2QString([device localizedName]),
.type = DeviceType::Camera,
};
}
} // namespace
EnvironmentMac::EnvironmentMac(not_null<EnvironmentDelegate*> delegate)
: _delegate(delegate)
, _captureMuteDebounceTimer([=] { sendCaptureMutedValue(); }) {
DefaultPlaybackDeviceChangedMonitor.registerEnvironment(this);
DefaultCaptureDeviceChangedMonitor.registerEnvironment(this);
AudioDeviceListChangedMonitor.registerEnvironment(this);
if (@available(macOS 14.0, *)) {
const auto weak = base::make_weak(this);
id block = [^(BOOL shouldBeMuted){
crl::on_main([weak, mute = shouldBeMuted ? true : false] {
if (const auto strong = weak.get()) {
if (strong->_captureMuteTracker) {
strong->_captureMuted = mute;
strong->_captureMuteDebounceTimer.callOnce(
kCaptureMuteDebounceTimeout);
}
}
});
return YES;
} copy];
_lifetime.add([block] { [block release]; });
[[AVAudioApplication sharedInstance]
setInputMuteStateChangeHandler:block
error:nil];
}
}
EnvironmentMac::~EnvironmentMac() {
DefaultPlaybackDeviceChangedMonitor.unregisterEnvironment(this);
DefaultCaptureDeviceChangedMonitor.unregisterEnvironment(this);
AudioDeviceListChangedMonitor.unregisterEnvironment(this);
}
void EnvironmentMac::sendCaptureMutedValue() {
Expects(_captureMuteTracker != nullptr);
const auto weak = base::make_weak(this);
_captureMuteNotification = true;
_captureMuteTracker->captureMuteChanged(_captureMuted);
if (const auto strong = weak.get()) {
strong->_captureMuteNotification = false;
}
}
void EnvironmentMac::defaultPlaybackDeviceChanged() {
const auto type = DeviceType::Playback;
_delegate->defaultChanged(type, DeviceChangeReason::Manual, defaultId(type));
}
void EnvironmentMac::defaultCaptureDeviceChanged() {
const auto type = DeviceType::Capture;
_delegate->defaultChanged(type, DeviceChangeReason::Manual, defaultId(type));
}
void EnvironmentMac::audioDeviceListChanged() {
_delegate->devicesForceRefresh(DeviceType::Playback);
_delegate->devicesForceRefresh(DeviceType::Capture);
}
QString EnvironmentMac::defaultId(DeviceType type) {
return DefaultId(type);
}
QString EnvironmentMac::DefaultId(DeviceType type) {
if (type == DeviceType::Camera) {
return CaptureDeviceId(
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]);
}
auto deviceId = AudioDeviceID(kAudioDeviceUnknown);
auto size = UInt32(sizeof(deviceId));
const auto address = AudioObjectPropertyAddress{
.mSelector = (type == DeviceType::Playback
? kAudioHardwarePropertyDefaultOutputDevice
: kAudioHardwarePropertyDefaultInputDevice),
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain,
};
const auto result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&address,
0,
0,
&size,
&deviceId);
return (result == kAudioHardwareNoError) ? GetDeviceUID(deviceId) : QString();
}
DeviceInfo EnvironmentMac::device(DeviceType type, const QString &id) {
if (type == DeviceType::Camera) {
NSArray<AVCaptureDevice*> *devices
= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) {
if (CaptureDeviceId(device) == id) {
return CaptureDeviceInfo(device);
}
}
return {};
}
const auto deviceId = GetDeviceByUID(id);
if (deviceId != kAudioDeviceUnknown) {
return {
.id = id,
.name = GetDeviceName(type, deviceId),
.type = type,
.inactive = false,
};
}
const auto list = devices(type);
return list.empty() ? DeviceInfo() : list.front();
}
std::vector<DeviceInfo> EnvironmentMac::devices(DeviceType type) {
if (type == DeviceType::Camera) {
auto result = std::vector<DeviceInfo>();
const auto add = [&](AVCaptureDevice *device) {
if (auto info = CaptureDeviceInfo(device)) {
if (!ranges::contains(result, info.id, &DeviceInfo::id)) {
result.push_back(std::move(info));
}
}
};
add([AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]);
NSArray<AVCaptureDevice*> *devices
= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) {
add(device);
}
return result;
}
auto result = std::vector<DeviceInfo>();
for (const auto deviceId : GetAllDeviceIds()) {
if (DeviceHasType(deviceId, type)) {
if (const auto uid = GetDeviceUID(deviceId); !uid.isEmpty()) {
result.push_back({
.id = uid,
.name = GetDeviceName(type, deviceId),
.type = type,
.inactive = false,
});
}
}
}
return result;
}
bool EnvironmentMac::refreshFullListOnChange(DeviceType type) {
// We have simple change notifications, no exact change information.
return true;
}
bool EnvironmentMac::desktopCaptureAllowed() const {
if (@available(macOS 11.0, *)) {
// Screen Recording is required on macOS 10.15 an later.
// Even if user grants access, restart is required.
static const auto result = CGPreflightScreenCaptureAccess();
return result;
} else if (@available(macOS 10.15, *)) {
const auto stream = CGDisplayStreamCreate(
CGMainDisplayID(),
1,
1,
kCVPixelFormatType_32BGRA,
CFDictionaryRef(),
^(
CGDisplayStreamFrameStatus status,
uint64_t display_time,
IOSurfaceRef frame_surface,
CGDisplayStreamUpdateRef updateRef) {
});
if (!stream) {
return false;
}
CFRelease(stream);
return true;
}
return true;
}
std::optional<QString> EnvironmentMac::uniqueDesktopCaptureSource() const {
return {};
}
void EnvironmentMac::defaultIdRequested(DeviceType type) {
}
void EnvironmentMac::devicesRequested(DeviceType type) {
}
void EnvironmentMac::setCaptureMuted(bool muted) {
if (@available(macOS 14.0, *)) {
if (!_captureMuteNotification) {
const auto value = muted ? YES : NO;
[[AVAudioApplication sharedInstance] setInputMuted:value error:nil];
}
}
}
void EnvironmentMac::captureMuteSubscribe() {
if (@available(macOS 14.0, *)) {
id observer = [[InputMuteObserver alloc] init];
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:observer
selector:@selector(inputMuteStateChange:)
name:AVAudioApplicationInputMuteStateChangeNotification
object:nil];
_admTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory();
const auto saveSetDeviceIdCallback = [=](
Fn<void(DeviceResolvedId)> setDeviceIdCallback) {
_admSetDeviceIdCallback = std::move(setDeviceIdCallback);
if (!_admCaptureDeviceId.isDefault()) {
_admSetDeviceIdCallback(_admCaptureDeviceId);
}
};
_adm = CreateAudioDeviceModule(
_admTaskQueueFactory.get(),
saveSetDeviceIdCallback);
// We don't need captured data, we need simply to have active recording.
static auto kEmptyCallback = EmptyCallback();
_adm->RegisterAudioCallback(&kEmptyCallback);
_captureMuteSubscriptionLifetime.add([=] {
_admSetDeviceIdCallback = nullptr;
_adm = nullptr;
_admTaskQueueFactory = nullptr;
[[[NSWorkspace sharedWorkspace] notificationCenter]
removeObserver:observer
name:AVAudioApplicationInputMuteStateChangeNotification
object:nil];
[observer release];
});
}
}
void EnvironmentMac::captureMuteUnsubscribe() {
_captureMuteSubscriptionLifetime.destroy();
}
void EnvironmentMac::captureMuteRestartAdm() {
_adm->StopRecording();
_adm->SetRecordingDevice(0);
if (_adm->InitRecording() == 0) {
_adm->StartRecording();
}
}
void EnvironmentMac::setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track) {
if (@available(macOS 14.0, *)) {
if (track) {
if (!_captureMuteTracker) {
captureMuteSubscribe();
} else if (_captureMuteTracker == tracker) {
return;
}
_captureMuteTrackerLifetime.destroy();
_captureMuteTracker = tracker;
_captureMuteTracker->captureMuteDeviceId(
) | rpl::on_next([=](DeviceResolvedId deviceId) {
_admSetDeviceIdCallback(deviceId);
captureMuteRestartAdm();
}, _captureMuteTrackerLifetime);
} else if (_captureMuteTracker == tracker) {
_captureMuteTrackerLifetime.destroy();
_captureMuteDebounceTimer.cancel();
_captureMuteTracker = nullptr;
captureMuteUnsubscribe();
if (!_captureMuted) {
_captureMuted = true;
setCaptureMuted(true);
}
}
}
}
std::unique_ptr<Environment> CreateEnvironment(
not_null<EnvironmentDelegate*> delegate) {
return std::make_unique<EnvironmentMac>(delegate);
}
} // namespace Webrtc::Platform