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
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:
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user