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,97 @@
|
||||
// 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/linux/webrtc_environment_linux.h"
|
||||
|
||||
#include "base/debug_log.h"
|
||||
|
||||
#ifdef WEBRTC_USE_PIPEWIRE
|
||||
#include <modules/desktop_capture/linux/wayland/base_capturer_pipewire.h>
|
||||
#include <modules/portal/pipewire_utils.h>
|
||||
#endif // WEBRTC_USE_PIPEWIRE
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
EnvironmentLinux::EnvironmentLinux(not_null<EnvironmentDelegate*> delegate)
|
||||
: _audioFallback(delegate)
|
||||
, _cameraFallback(delegate) {
|
||||
#ifdef WEBRTC_USE_PIPEWIRE
|
||||
if (!webrtc::InitializePipeWire()) {
|
||||
LOG(("Audio Info: Failed to load pipewire 0.3 stubs."));
|
||||
}
|
||||
#endif // WEBRTC_USE_PIPEWIRE
|
||||
}
|
||||
|
||||
EnvironmentLinux::~EnvironmentLinux() {
|
||||
}
|
||||
|
||||
QString EnvironmentLinux::defaultId(DeviceType type) {
|
||||
return (type == DeviceType::Camera)
|
||||
? _cameraFallback.defaultId(type)
|
||||
: _audioFallback.defaultId(type);
|
||||
}
|
||||
|
||||
DeviceInfo EnvironmentLinux::device(DeviceType type, const QString &id) {
|
||||
return (type == DeviceType::Camera)
|
||||
? _cameraFallback.device(type, id)
|
||||
: _audioFallback.device(type, id);
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> EnvironmentLinux::devices(DeviceType type) {
|
||||
return (type == DeviceType::Camera)
|
||||
? _cameraFallback.devices(type)
|
||||
: _audioFallback.devices(type);
|
||||
}
|
||||
|
||||
bool EnvironmentLinux::refreshFullListOnChange(DeviceType type) {
|
||||
return (type == DeviceType::Camera)
|
||||
? _cameraFallback.refreshFullListOnChange(type)
|
||||
: _audioFallback.refreshFullListOnChange(type);
|
||||
}
|
||||
|
||||
bool EnvironmentLinux::desktopCaptureAllowed() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<QString> EnvironmentLinux::uniqueDesktopCaptureSource() const {
|
||||
#ifdef WEBRTC_USE_PIPEWIRE
|
||||
if (webrtc::DesktopCapturer::IsRunningUnderWayland()) {
|
||||
return u"desktop_capturer_pipewire"_q;
|
||||
}
|
||||
#endif // WEBRTC_USE_PIPEWIRE
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void EnvironmentLinux::defaultIdRequested(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
_cameraFallback.defaultIdRequested(type);
|
||||
} else {
|
||||
_audioFallback.defaultIdRequested(type);
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentLinux::devicesRequested(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
_cameraFallback.devicesRequested(type);
|
||||
} else {
|
||||
_audioFallback.devicesRequested(type);
|
||||
}
|
||||
}
|
||||
|
||||
DeviceResolvedId EnvironmentLinux::threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) {
|
||||
return (lastResolvedId.type == DeviceType::Camera)
|
||||
? _cameraFallback.threadSafeResolveId(lastResolvedId, savedId)
|
||||
: _audioFallback.threadSafeResolveId(lastResolvedId, savedId);
|
||||
}
|
||||
|
||||
std::unique_ptr<Environment> CreateEnvironment(
|
||||
not_null<EnvironmentDelegate*> delegate) {
|
||||
return std::make_unique<EnvironmentLinux>(delegate);
|
||||
}
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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 "webrtc/platform/webrtc_platform_environment.h"
|
||||
|
||||
#include "webrtc/details/webrtc_environment_openal.h"
|
||||
#include "webrtc/details/webrtc_environment_video_capture.h"
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
class EnvironmentLinux final : public Environment {
|
||||
public:
|
||||
explicit EnvironmentLinux(not_null<EnvironmentDelegate*> delegate);
|
||||
~EnvironmentLinux();
|
||||
|
||||
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;
|
||||
|
||||
DeviceResolvedId threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) override;
|
||||
|
||||
private:
|
||||
details::EnvironmentOpenAL _audioFallback;
|
||||
details::EnvironmentVideoCapture _cameraFallback;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -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
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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 "webrtc/webrtc_device_common.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
class EnvironmentDelegate;
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
virtual ~Environment() = default;
|
||||
|
||||
[[nodiscard]] virtual QString defaultId(DeviceType type) = 0;
|
||||
[[nodiscard]] virtual DeviceInfo device(
|
||||
DeviceType type,
|
||||
const QString &id) = 0;
|
||||
|
||||
[[nodiscard]] virtual std::vector<DeviceInfo> devices(
|
||||
DeviceType type) = 0;
|
||||
[[nodiscard]] virtual bool refreshFullListOnChange(
|
||||
DeviceType type) = 0;
|
||||
|
||||
[[nodiscard]] virtual bool desktopCaptureAllowed() const = 0;
|
||||
[[nodiscard]] virtual auto uniqueDesktopCaptureSource() const
|
||||
-> std::optional<QString> = 0;
|
||||
|
||||
virtual void defaultIdRequested(DeviceType type) = 0;
|
||||
virtual void devicesRequested(DeviceType type) = 0;
|
||||
|
||||
[[nodiscard]] virtual DeviceResolvedId threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) {
|
||||
return lastResolvedId;
|
||||
}
|
||||
|
||||
virtual void setCaptureMuted(bool muted) {
|
||||
}
|
||||
virtual void setCaptureMuteTracker(
|
||||
not_null<CaptureMuteTracker*> tracker,
|
||||
bool track) {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Environment> CreateEnvironment(
|
||||
not_null<EnvironmentDelegate*> delegate);
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,408 @@
|
||||
// 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/win/webrtc_environment_win.h"
|
||||
|
||||
#include "base/platform/win/base_windows_co_task_mem.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "webrtc/webrtc_environment.h"
|
||||
|
||||
#include <MMDeviceAPI.h>
|
||||
#include <winrt/base.h>
|
||||
|
||||
#include <functiondiscoverykeys_devpkey.h>
|
||||
#include <propvarutil.h>
|
||||
#include <propkey.h>
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxNameLength = 256;
|
||||
|
||||
[[nodiscard]] auto RoleForType(DeviceType type) {
|
||||
return (type == DeviceType::Playback) ? eConsole : eCommunications;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class EnvironmentWin::Client
|
||||
: public winrt::implements<Client, IMMNotificationClient>
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
Client(
|
||||
Fn<void(DeviceType, QString)> defaultChanged,
|
||||
Fn<void(QString, std::optional<DeviceStateChange>)> deviceToggled);
|
||||
|
||||
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
|
||||
LPCWSTR deviceId,
|
||||
const PROPERTYKEY key) override;
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR deviceId) override;
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR deviceId) override;
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
|
||||
LPCWSTR deviceId,
|
||||
DWORD newState) override;
|
||||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(
|
||||
EDataFlow flow,
|
||||
ERole role,
|
||||
LPCWSTR newDefaultDeviceId) override;
|
||||
|
||||
private:
|
||||
Fn<void(DeviceType, QString)> _defaultChanged;
|
||||
Fn<void(QString, std::optional<DeviceStateChange>)> _deviceToggled;
|
||||
|
||||
};
|
||||
|
||||
EnvironmentWin::Client::Client(
|
||||
Fn<void(DeviceType, QString)> defaultChanged,
|
||||
Fn<void(QString, std::optional<DeviceStateChange>)> deviceToggled)
|
||||
: _defaultChanged(std::move(defaultChanged))
|
||||
, _deviceToggled(std::move(deviceToggled)) {
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnPropertyValueChanged(
|
||||
LPCWSTR deviceId,
|
||||
const PROPERTYKEY key) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnDeviceAdded(
|
||||
LPCWSTR deviceId) {
|
||||
const auto id = QString::fromWCharArray(deviceId);
|
||||
crl::on_main(this, [=] {
|
||||
const auto onstack = _deviceToggled;
|
||||
onstack(id, std::nullopt);
|
||||
});
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnDeviceRemoved(
|
||||
LPCWSTR deviceId) {
|
||||
const auto id = QString::fromWCharArray(deviceId);
|
||||
const auto change = DeviceStateChange::Disconnected;
|
||||
crl::on_main(this, [=] {
|
||||
const auto onstack = _deviceToggled;
|
||||
onstack(id, change);
|
||||
});
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnDeviceStateChanged(
|
||||
LPCWSTR deviceId,
|
||||
DWORD newState) {
|
||||
const auto change = (newState == DEVICE_STATE_ACTIVE)
|
||||
? DeviceStateChange::Active
|
||||
: DeviceStateChange::Inactive;
|
||||
const auto id = QString::fromWCharArray(deviceId);
|
||||
crl::on_main(this, [=] {
|
||||
const auto onstack = _deviceToggled;
|
||||
onstack(id, change);
|
||||
});
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnDefaultDeviceChanged(
|
||||
EDataFlow flow,
|
||||
ERole role,
|
||||
LPCWSTR newDefaultDeviceId) {
|
||||
const auto type = (flow == eRender)
|
||||
? DeviceType::Playback
|
||||
: DeviceType::Capture;
|
||||
if (role == RoleForType(type)) {
|
||||
const auto id = QString::fromWCharArray(newDefaultDeviceId);
|
||||
crl::on_main(this, [=] {
|
||||
const auto onstack = _defaultChanged;
|
||||
onstack(type, id);
|
||||
});
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
EnvironmentWin::EnvironmentWin(not_null<EnvironmentDelegate*> delegate)
|
||||
: _delegate(delegate)
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
, _audioFallback(delegate)
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
, _cameraFallback(delegate) {
|
||||
#ifndef WEBRTC_TESTING_OPENAL
|
||||
using namespace base::WinRT;
|
||||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||||
_enumerator = TryCreateInstance<IMMDeviceEnumerator>(
|
||||
CLSID_MMDeviceEnumerator);
|
||||
if (!_enumerator) {
|
||||
const auto hr = CoInitialize(nullptr);
|
||||
if (SUCCEEDED(hr)) {
|
||||
_comInitialized = true;
|
||||
_enumerator = TryCreateInstance<IMMDeviceEnumerator>(
|
||||
CLSID_MMDeviceEnumerator);
|
||||
if (!_enumerator) {
|
||||
LOG(("Media Error: Could not create MMDeviceEnumerator."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_client = winrt::make<Client>([=](DeviceType type, QString id) {
|
||||
_delegate->defaultChanged(type, DeviceChangeReason::Manual, id);
|
||||
}, [=](QString id, std::optional<DeviceStateChange> state) {
|
||||
processDeviceStateChange(id, state);
|
||||
});
|
||||
if (!_client) {
|
||||
LOG(("Media Error: Could not create IMMNotificationClient."));
|
||||
return;
|
||||
}
|
||||
const auto hr = _enumerator->RegisterEndpointNotificationCallback(
|
||||
_client.get());
|
||||
if (FAILED(hr)) {
|
||||
LOG(("Media Error: RegisterEndpointNotificationCallback failed."));
|
||||
}
|
||||
#endif // !WEBRTC_TESTING_OPENAL
|
||||
}
|
||||
|
||||
EnvironmentWin::~EnvironmentWin() {
|
||||
if (_client) {
|
||||
Assert(_enumerator != nullptr);
|
||||
_enumerator->UnregisterEndpointNotificationCallback(_client.get());
|
||||
_client = nullptr;
|
||||
}
|
||||
_enumerator = nullptr;
|
||||
if (_comInitialized) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
QString EnvironmentWin::defaultId(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return _cameraFallback.defaultId(type);
|
||||
} else if (!_enumerator) {
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
return _audioFallback.defaultId(type);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
return {};
|
||||
}
|
||||
const auto flow = (type == DeviceType::Playback)
|
||||
? eRender
|
||||
: eCapture;
|
||||
const auto role = RoleForType(type);
|
||||
|
||||
auto device = winrt::com_ptr<IMMDevice>();
|
||||
auto hr = _enumerator->GetDefaultAudioEndpoint(flow, role, device.put());
|
||||
if (FAILED(hr) || !device) {
|
||||
return {};
|
||||
}
|
||||
auto id = base::CoTaskMemString();
|
||||
hr = device->GetId(id.put());
|
||||
if (FAILED(hr) || !id || !*id.data()) {
|
||||
return {};
|
||||
}
|
||||
return QString::fromWCharArray(id.data());
|
||||
}
|
||||
|
||||
void EnvironmentWin::processDeviceStateChange(
|
||||
const QString &id,
|
||||
std::optional<DeviceStateChange> change) {
|
||||
const auto wide = id.toStdWString();
|
||||
auto device = winrt::com_ptr<IMMDevice>();
|
||||
auto hr = _enumerator->GetDevice(wide.c_str(), device.put());
|
||||
if (FAILED(hr)) {
|
||||
return;
|
||||
} else if (const auto endpoint = device.try_as<IMMEndpoint>()) {
|
||||
auto flow = EDataFlow();
|
||||
hr = endpoint->GetDataFlow(&flow);
|
||||
if (SUCCEEDED(hr)) {
|
||||
if (!change) {
|
||||
auto state = DWORD();
|
||||
hr = device->GetState(&state);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return;
|
||||
}
|
||||
change = (state == DEVICE_STATE_ACTIVE)
|
||||
? DeviceStateChange::Active
|
||||
: DeviceStateChange::Inactive;
|
||||
}
|
||||
const auto type = (flow == eRender)
|
||||
? DeviceType::Playback
|
||||
: DeviceType::Capture;
|
||||
_delegate->deviceStateChanged(type, id, *change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeviceInfo EnvironmentWin::device(DeviceType type, const QString &id) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return _cameraFallback.device(type, id);
|
||||
} else if (!_enumerator) {
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
return _audioFallback.device(type, id);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
return {};
|
||||
}
|
||||
const auto wide = id.toStdWString();
|
||||
auto device = winrt::com_ptr<IMMDevice>();
|
||||
auto hr = _enumerator->GetDevice(wide.c_str(), device.put());
|
||||
if (FAILED(hr) || !device) {
|
||||
return {};
|
||||
}
|
||||
auto store = winrt::com_ptr<IPropertyStore>();
|
||||
hr = device->OpenPropertyStore(STGM_READ, store.put());
|
||||
if (FAILED(hr) || !store) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto name = PROPVARIANT();
|
||||
hr = store->GetValue(PKEY_Device_FriendlyName, &name);
|
||||
if (FAILED(hr)) {
|
||||
return {};
|
||||
}
|
||||
const auto guard = gsl::finally([&] { PropVariantClear(&name); });
|
||||
|
||||
auto already = std::array<WCHAR, kMaxNameLength>();
|
||||
hr = PropVariantToString(name, already.data(), MAX_PATH);
|
||||
if (FAILED(hr) || !already[0]) {
|
||||
return {};
|
||||
}
|
||||
auto state = DWORD();
|
||||
hr = device->GetState(&state);
|
||||
if (FAILED(hr)) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
.id = id,
|
||||
.name = QString::fromWCharArray(already.data()),
|
||||
.type = type,
|
||||
.inactive = (state != DEVICE_STATE_ACTIVE),
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> EnvironmentWin::devices(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return _cameraFallback.devices(type);
|
||||
} else if (!_enumerator) {
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
return _audioFallback.devices(type);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
return {};
|
||||
}
|
||||
const auto flow = (type == DeviceType::Playback)
|
||||
? eRender
|
||||
: eCapture;
|
||||
auto collection = winrt::com_ptr<IMMDeviceCollection>();
|
||||
auto hr = _enumerator->EnumAudioEndpoints(
|
||||
flow,
|
||||
DEVICE_STATEMASK_ALL,
|
||||
collection.put());
|
||||
if (FAILED(hr) || !collection) {
|
||||
return {};
|
||||
}
|
||||
auto count = UINT();
|
||||
hr = collection->GetCount(&count);
|
||||
if (FAILED(hr)) {
|
||||
return {};
|
||||
}
|
||||
auto result = std::vector<DeviceInfo>();
|
||||
result.reserve(count);
|
||||
for (auto i = UINT(); i != count; ++i) {
|
||||
auto device = winrt::com_ptr<IMMDevice>();
|
||||
hr = collection->Item(i, device.put());
|
||||
if (FAILED(hr)) {
|
||||
continue;
|
||||
}
|
||||
auto endpoint = device.try_as<IMMEndpoint>();
|
||||
if (!endpoint) {
|
||||
continue;
|
||||
}
|
||||
auto flow = EDataFlow();
|
||||
hr = endpoint->GetDataFlow(&flow);
|
||||
auto id = base::CoTaskMemString();
|
||||
hr = device->GetId(id.put());
|
||||
if (FAILED(hr) || !id || !*id.data()) {
|
||||
continue;
|
||||
}
|
||||
auto store = winrt::com_ptr<IPropertyStore>();
|
||||
hr = device->OpenPropertyStore(STGM_READ, store.put());
|
||||
if (FAILED(hr) || !store) {
|
||||
continue;
|
||||
}
|
||||
auto name = PROPVARIANT();
|
||||
hr = store->GetValue(PKEY_Device_FriendlyName, &name);
|
||||
if (FAILED(hr)) {
|
||||
continue;
|
||||
}
|
||||
const auto guard = gsl::finally([&] { PropVariantClear(&name); });
|
||||
|
||||
auto already = std::array<WCHAR, kMaxNameLength>();
|
||||
hr = PropVariantToString(name, already.data(), MAX_PATH);
|
||||
if (FAILED(hr) || !already[0]) {
|
||||
continue;
|
||||
}
|
||||
auto state = DWORD();
|
||||
hr = device->GetState(&state);
|
||||
if (FAILED(hr)) {
|
||||
continue;
|
||||
}
|
||||
result.push_back({
|
||||
.id = QString::fromWCharArray(id.data()),
|
||||
.name = QString::fromWCharArray(already.data()),
|
||||
.type = type,
|
||||
.inactive = (state != DEVICE_STATE_ACTIVE),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EnvironmentWin::refreshFullListOnChange(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return _cameraFallback.refreshFullListOnChange(type);
|
||||
}
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
return true;
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EnvironmentWin::desktopCaptureAllowed() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<QString> EnvironmentWin::uniqueDesktopCaptureSource() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
void EnvironmentWin::defaultIdRequested(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
_cameraFallback.defaultIdRequested(type);
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
} else {
|
||||
_audioFallback.defaultIdRequested(type);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentWin::devicesRequested(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
_cameraFallback.devicesRequested(type);
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
} else {
|
||||
_audioFallback.devicesRequested(type);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
}
|
||||
}
|
||||
|
||||
DeviceResolvedId EnvironmentWin::threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) {
|
||||
return (lastResolvedId.type == DeviceType::Camera)
|
||||
? _cameraFallback.threadSafeResolveId(lastResolvedId, savedId)
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
: _audioFallback.threadSafeResolveId(lastResolvedId, savedId);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
: lastResolvedId;
|
||||
}
|
||||
|
||||
std::unique_ptr<Environment> CreateEnvironment(
|
||||
not_null<EnvironmentDelegate*> delegate) {
|
||||
return std::make_unique<EnvironmentWin>(delegate);
|
||||
}
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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 "webrtc/platform/webrtc_platform_environment.h"
|
||||
|
||||
#include "webrtc/details/webrtc_environment_video_capture.h"
|
||||
#include "base/platform/win/base_windows_winrt.h"
|
||||
|
||||
//#define WEBRTC_TESTING_OPENAL
|
||||
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
#include "webrtc/details/webrtc_environment_openal.h"
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
|
||||
struct IMMDeviceEnumerator;
|
||||
struct IMMNotificationClient;
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
class EnvironmentWin final : public Environment {
|
||||
public:
|
||||
explicit EnvironmentWin(not_null<EnvironmentDelegate*> delegate);
|
||||
~EnvironmentWin();
|
||||
|
||||
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;
|
||||
|
||||
DeviceResolvedId threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) override;
|
||||
|
||||
private:
|
||||
void processDeviceStateChange(
|
||||
const QString &id,
|
||||
std::optional<DeviceStateChange> change);
|
||||
|
||||
class Client;
|
||||
|
||||
const not_null<EnvironmentDelegate*> _delegate;
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
details::EnvironmentOpenAL _audioFallback;
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
details::EnvironmentVideoCapture _cameraFallback;
|
||||
|
||||
bool _comInitialized = false;
|
||||
winrt::com_ptr<IMMDeviceEnumerator> _enumerator;
|
||||
winrt::com_ptr<IMMNotificationClient> _client;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
1149
Telegram/lib_webrtc/webrtc/platform/win/webrtc_loopback_adm_win.cpp
Normal file
1149
Telegram/lib_webrtc/webrtc/platform/win/webrtc_loopback_adm_win.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,198 @@
|
||||
// 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 <modules/audio_device/include/audio_device.h>
|
||||
#include <modules/audio_device/audio_device_buffer.h>
|
||||
#include <api/scoped_refptr.h>
|
||||
|
||||
#include <AudioClient.h>
|
||||
#include <MMDeviceAPI.h>
|
||||
|
||||
#include <winrt/base.h>
|
||||
|
||||
typedef struct SwrContext SwrContext;
|
||||
typedef struct AVChannelLayout AVChannelLayout;
|
||||
|
||||
namespace rtc {
|
||||
class Thread;
|
||||
} // namespace rtc
|
||||
|
||||
namespace webrtc {
|
||||
class AudioProcessing;
|
||||
class AudioFrame;
|
||||
} // namespace webrtc
|
||||
|
||||
namespace Webrtc::details {
|
||||
|
||||
[[nodiscard]] bool IsLoopbackCaptureActive();
|
||||
|
||||
void LoopbackCapturePushFarEnd(
|
||||
crl::time when,
|
||||
const QByteArray &samples,
|
||||
int frequency,
|
||||
int channels);
|
||||
|
||||
class AudioDeviceLoopbackWin : public webrtc::AudioDeviceModule {
|
||||
public:
|
||||
explicit AudioDeviceLoopbackWin(webrtc::TaskQueueFactory *taskQueueFactory);
|
||||
~AudioDeviceLoopbackWin();
|
||||
|
||||
int32_t ActiveAudioLayer(AudioLayer *audioLayer) const override;
|
||||
int32_t RegisterAudioCallback(
|
||||
webrtc::AudioTransport *audioCallback) override;
|
||||
|
||||
// Main initializaton and termination
|
||||
int32_t Init() override;
|
||||
int32_t Terminate() override;
|
||||
bool Initialized() const override;
|
||||
|
||||
// Device enumeration
|
||||
int16_t PlayoutDevices() override;
|
||||
int16_t RecordingDevices() override;
|
||||
int32_t PlayoutDeviceName(uint16_t index,
|
||||
char name[webrtc::kAdmMaxDeviceNameSize],
|
||||
char guid[webrtc::kAdmMaxGuidSize]) override;
|
||||
int32_t RecordingDeviceName(uint16_t index,
|
||||
char name[webrtc::kAdmMaxDeviceNameSize],
|
||||
char guid[webrtc::kAdmMaxGuidSize]) override;
|
||||
|
||||
// Device selection
|
||||
int32_t SetPlayoutDevice(uint16_t index) override;
|
||||
int32_t SetPlayoutDevice(WindowsDeviceType device) override;
|
||||
int32_t SetRecordingDevice(uint16_t index) override;
|
||||
int32_t SetRecordingDevice(WindowsDeviceType device) override;
|
||||
|
||||
// Audio transport initialization
|
||||
int32_t PlayoutIsAvailable(bool *available) override;
|
||||
int32_t InitPlayout() override;
|
||||
bool PlayoutIsInitialized() const override;
|
||||
int32_t RecordingIsAvailable(bool *available) override;
|
||||
int32_t InitRecording() override;
|
||||
bool RecordingIsInitialized() const override;
|
||||
|
||||
// Audio transport control
|
||||
int32_t StartPlayout() override;
|
||||
int32_t StopPlayout() override;
|
||||
bool Playing() const override;
|
||||
int32_t StartRecording() override;
|
||||
int32_t StopRecording() override;
|
||||
bool Recording() const override;
|
||||
|
||||
// Audio mixer initialization
|
||||
int32_t InitSpeaker() override;
|
||||
bool SpeakerIsInitialized() const override;
|
||||
int32_t InitMicrophone() override;
|
||||
bool MicrophoneIsInitialized() const override;
|
||||
|
||||
// Speaker volume controls
|
||||
int32_t SpeakerVolumeIsAvailable(bool *available) override;
|
||||
int32_t SetSpeakerVolume(uint32_t volume) override;
|
||||
int32_t SpeakerVolume(uint32_t *volume) const override;
|
||||
int32_t MaxSpeakerVolume(uint32_t *maxVolume) const override;
|
||||
int32_t MinSpeakerVolume(uint32_t *minVolume) const override;
|
||||
|
||||
// Microphone volume controls
|
||||
int32_t MicrophoneVolumeIsAvailable(bool *available) override;
|
||||
int32_t SetMicrophoneVolume(uint32_t volume) override;
|
||||
int32_t MicrophoneVolume(uint32_t *volume) const override;
|
||||
int32_t MaxMicrophoneVolume(uint32_t *maxVolume) const override;
|
||||
int32_t MinMicrophoneVolume(uint32_t *minVolume) const override;
|
||||
|
||||
// Microphone mute control
|
||||
int32_t MicrophoneMuteIsAvailable(bool *available) override;
|
||||
int32_t SetMicrophoneMute(bool enable) override;
|
||||
int32_t MicrophoneMute(bool *enabled) const override;
|
||||
|
||||
// Speaker mute control
|
||||
int32_t SpeakerMuteIsAvailable(bool *available) override;
|
||||
int32_t SetSpeakerMute(bool enable) override;
|
||||
int32_t SpeakerMute(bool *enabled) const override;
|
||||
|
||||
// Stereo support
|
||||
int32_t StereoPlayoutIsAvailable(bool *available) const override;
|
||||
int32_t SetStereoPlayout(bool enable) override;
|
||||
int32_t StereoPlayout(bool *enabled) const override;
|
||||
int32_t StereoRecordingIsAvailable(bool *available) const override;
|
||||
int32_t SetStereoRecording(bool enable) override;
|
||||
int32_t StereoRecording(bool *enabled) const override;
|
||||
|
||||
// Delay information and control
|
||||
int32_t PlayoutDelay(uint16_t *delayMS) const override;
|
||||
|
||||
// Only supported on Android.
|
||||
bool BuiltInAECIsAvailable() const override;
|
||||
bool BuiltInAGCIsAvailable() const override;
|
||||
bool BuiltInNSIsAvailable() const override;
|
||||
|
||||
// Enables the built-in audio effects. Only supported on Android.
|
||||
int32_t EnableBuiltInAEC(bool enable) override;
|
||||
int32_t EnableBuiltInAGC(bool enable) override;
|
||||
int32_t EnableBuiltInNS(bool enable) override;
|
||||
|
||||
private:
|
||||
void openPlaybackDeviceForCapture();
|
||||
void openAudioClient();
|
||||
|
||||
void openRecordingDevice();
|
||||
void closeRecordingDevice();
|
||||
|
||||
void captureFailed(const std::string &error);
|
||||
|
||||
void startCaptureOnThread();
|
||||
void stopCaptureOnThread();
|
||||
void processData();
|
||||
|
||||
bool setupResampler(const WAVEFORMATEX &format);
|
||||
bool setupResampler(
|
||||
int channels,
|
||||
AVChannelLayout channelLayout,
|
||||
int inputFormat, // AVSampleFormat
|
||||
int sampleRate,
|
||||
Fn<std::string()> info);
|
||||
void ensureResampleSpaceAvailable(int samples);
|
||||
|
||||
static DWORD WINAPI CaptureThreadMethod(LPVOID context);
|
||||
DWORD runCaptureThread();
|
||||
|
||||
webrtc::AudioDeviceBuffer _audioDeviceBuffer;
|
||||
|
||||
winrt::com_ptr<IMMDevice> _endpointDevice;
|
||||
winrt::com_ptr<IAudioClient> _audioClient;
|
||||
winrt::com_ptr<IAudioClient> _audioRenderClientForLoopback;
|
||||
winrt::com_ptr<IAudioCaptureClient> _audioCaptureClient;
|
||||
|
||||
rtc::scoped_refptr<webrtc::AudioProcessing> _audioProcessing;
|
||||
std::unique_ptr<webrtc::AudioFrame> _capturedFrame;
|
||||
std::unique_ptr<webrtc::AudioFrame> _renderedFrame;
|
||||
|
||||
HANDLE _thread = nullptr;
|
||||
HANDLE _audioSamplesReadyEvent = nullptr;
|
||||
HANDLE _captureThreadShutdownEvent = nullptr;
|
||||
|
||||
UINT32 _bufferSizeFrames = 0;
|
||||
UINT32 _deviceFrameSize = 0;
|
||||
QByteArray _deviceBuffer;
|
||||
QByteArray _resampleBuffer;
|
||||
UINT32 _bufferOffset = 0;
|
||||
|
||||
double _queryPerformanceMultiplier = 0.;
|
||||
double _deviceFrequencyMultiplier = 0.;
|
||||
|
||||
SwrContext *_swrContext = nullptr;
|
||||
int _swrSrcSampleRate = 0;
|
||||
|
||||
bool _microphoneInitialized = false;
|
||||
bool _initialized = false;
|
||||
|
||||
bool _recordingInitialized = false;
|
||||
bool _recordingFailed = false;
|
||||
bool _recording = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webrtc::details
|
||||
Reference in New Issue
Block a user