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
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s

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,73 @@
# 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
add_library(lib_webrtc OBJECT)
add_library(desktop-app::lib_webrtc ALIAS lib_webrtc)
init_target(lib_webrtc)
get_filename_component(src_loc . REALPATH)
target_precompile_headers(lib_webrtc PRIVATE ${src_loc}/webrtc/webrtc_pch.h)
nice_target_sources(lib_webrtc ${src_loc}
PRIVATE
webrtc/webrtc_audio_input_tester.cpp
webrtc/webrtc_audio_input_tester.h
webrtc/webrtc_create_adm.cpp
webrtc/webrtc_create_adm.h
webrtc/webrtc_device_common.h
webrtc/webrtc_device_resolver.cpp
webrtc/webrtc_device_resolver.h
webrtc/webrtc_environment.cpp
webrtc/webrtc_environment.h
webrtc/webrtc_video_track.cpp
webrtc/webrtc_video_track.h
webrtc/details/webrtc_environment_openal.cpp
webrtc/details/webrtc_environment_openal.h
webrtc/details/webrtc_environment_video_capture.cpp
webrtc/details/webrtc_environment_video_capture.h
webrtc/details/webrtc_openal_adm.cpp
webrtc/details/webrtc_openal_adm.h
webrtc/platform/linux/webrtc_environment_linux.cpp
webrtc/platform/linux/webrtc_environment_linux.h
webrtc/platform/mac/webrtc_environment_mac.h
webrtc/platform/mac/webrtc_environment_mac.mm
webrtc/platform/win/webrtc_environment_win.cpp
webrtc/platform/win/webrtc_environment_win.h
webrtc/platform/win/webrtc_loopback_adm_win.cpp
webrtc/platform/win/webrtc_loopback_adm_win.h
webrtc/platform/webrtc_platform_environment.h
webrtc/webrtc_pch.h
)
if (WIN32)
target_compile_definitions(lib_webrtc
PRIVATE
WEBRTC_WIN
)
elseif (APPLE)
target_compile_definitions(lib_webrtc
PRIVATE
WEBRTC_MAC
)
endif()
target_include_directories(lib_webrtc
PUBLIC
${src_loc}
)
target_link_libraries(lib_webrtc
PUBLIC
desktop-app::lib_base
desktop-app::lib_ffmpeg
desktop-app::external_qt
desktop-app::external_openal
PRIVATE
desktop-app::external_webrtc
)

View File

@@ -0,0 +1,158 @@
// 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/details/webrtc_environment_openal.h"
#include "webrtc/webrtc_environment.h"
#include <al.h>
#include <alc.h>
namespace Webrtc::details {
namespace {
template <typename Callback>
void EnumerateDevices(DeviceType type, Callback &&callback) {
const auto specifier = (type == DeviceType::Playback)
? ALC_ALL_DEVICES_SPECIFIER
: ALC_CAPTURE_DEVICE_SPECIFIER;
auto devices = alcGetString(nullptr, specifier);
Assert(devices != nullptr);
while (*devices != 0) {
callback(devices);
while (*devices != 0) {
++devices;
}
++devices;
}
}
[[nodiscard]] DeviceInfo DeviceFromOpenAL(
DeviceType type,
const char *device) {
if (!device) {
return {};
}
const auto guid = QString::fromUtf8(device);
const auto prefix = u"OpenAL Soft on "_q;
return {
.id = guid,
.name = (guid.startsWith(prefix) ? guid.mid(prefix.size()) : guid),
.type = type,
};
}
} // namespace
EnvironmentOpenAL::EnvironmentOpenAL(not_null<EnvironmentDelegate*> delegate)
: _delegate(delegate) {
}
EnvironmentOpenAL::~EnvironmentOpenAL() {
}
QString EnvironmentOpenAL::defaultId(DeviceType type) {
return DefaultId(type);
}
QString EnvironmentOpenAL::DefaultId(DeviceType type) {
Expects(type == DeviceType::Playback || type == DeviceType::Capture);
[[maybe_unused]] const auto reenumerate = alcGetString(
nullptr,
(type == DeviceType::Capture)
? ALC_CAPTURE_DEVICE_SPECIFIER
: ALC_ALL_DEVICES_SPECIFIER);
return QString::fromUtf8(
alcGetString(nullptr, (type == DeviceType::Capture)
? ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER
: ALC_DEFAULT_ALL_DEVICES_SPECIFIER));
}
DeviceResolvedId EnvironmentOpenAL::DefaultResolvedId(DeviceType type) {
return { DefaultId(type), type, true };
}
DeviceResolvedId EnvironmentOpenAL::ResolveId(
DeviceType type,
const QString &savedId) {
if (savedId.isEmpty() || savedId == kDefaultDeviceId) {
return DefaultResolvedId(type);
}
auto found = false;
EnumerateDevices(type, [&](const char *device) {
const auto info = DeviceFromOpenAL(type, device);
if (info.id == savedId) {
found = true;
}
});
return found
? DeviceResolvedId{ savedId, type }
: DefaultResolvedId(type);
}
DeviceInfo EnvironmentOpenAL::device(DeviceType type, const QString &id) {
Expects(type == DeviceType::Playback || type == DeviceType::Capture);
auto result = DeviceInfo();
EnumerateDevices(type, [&](const char *device) {
auto info = DeviceFromOpenAL(type, device);
if (info.id == id) {
result = std::move(info);
}
});
return result;
}
std::vector<DeviceInfo> EnvironmentOpenAL::devices(DeviceType type) {
Expects(type == DeviceType::Playback || type == DeviceType::Capture);
auto result = std::vector<DeviceInfo>();
EnumerateDevices(type, [&](const char *device) {
if (auto info = DeviceFromOpenAL(type, device)) {
result.push_back(std::move(info));
}
});
return result;
}
bool EnvironmentOpenAL::refreshFullListOnChange(DeviceType type) {
Expects(type == DeviceType::Playback || type == DeviceType::Capture);
return true;
}
bool EnvironmentOpenAL::desktopCaptureAllowed() const {
Unexpected("EnvironmentOpenAL::desktopCaptureAllowed.");
}
auto EnvironmentOpenAL::uniqueDesktopCaptureSource() const
-> std::optional<QString> {
Unexpected("EnvironmentOpenAL::uniqueDesktopCaptureSource.");
}
void EnvironmentOpenAL::defaultIdRequested(DeviceType type) {
_delegate->devicesForceRefresh(type);
}
void EnvironmentOpenAL::devicesRequested(DeviceType type) {
_delegate->devicesForceRefresh(type);
}
DeviceResolvedId EnvironmentOpenAL::threadSafeResolveId(
const DeviceResolvedId &lastResolvedId,
const QString &savedId) {
const auto result = ResolveId(lastResolvedId.type, savedId);
if (result != lastResolvedId) {
crl::on_main(this, [=, type = lastResolvedId.type] {
_delegate->devicesForceRefresh(type);
});
}
return result;
}
} // namespace Webrtc::details

View File

@@ -0,0 +1,51 @@
// 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/weak_ptr.h"
#include "webrtc/platform/webrtc_platform_environment.h"
namespace Webrtc::details {
class EnvironmentOpenAL final
: public Platform::Environment
, public base::has_weak_ptr {
public:
using EnvironmentDelegate = Platform::EnvironmentDelegate;
explicit EnvironmentOpenAL(not_null<EnvironmentDelegate*> delegate);
~EnvironmentOpenAL();
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;
[[nodiscard]] static QString DefaultId(DeviceType type);
private:
[[nodiscard]] static DeviceResolvedId DefaultResolvedId(DeviceType type);
[[nodiscard]] static DeviceResolvedId ResolveId(
DeviceType type,
const QString &savedId);
const not_null<EnvironmentDelegate*> _delegate;
};
} // namespace Webrtc::details

View File

@@ -0,0 +1,133 @@
// 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/details/webrtc_environment_video_capture.h"
#include "webrtc/webrtc_environment.h"
#include <api/task_queue/default_task_queue_factory.h>
#include <modules/video_capture/video_capture_factory.h>
#include <modules/audio_device/include/audio_device_factory.h>
#ifdef WEBRTC_LINUX // Breaks compilation on MSVC because of ERROR define.
#include <modules/video_capture/video_capture_options.h>
#endif // WEBRTC_LINUX
namespace Webrtc::details {
namespace {
#ifdef WEBRTC_LINUX
[[nodiscard]] webrtc::VideoCaptureOptions GetVideoCaptureOptions() {
auto result = webrtc::VideoCaptureOptions();
result.set_allow_v4l2(true);
#ifdef WEBRTC_USE_PIPEWIRE
//result.set_allow_pipewire(true);
// This requires a call to result.Init(callback)
// and waiting for the callback to finish.
#endif
return result;
}
#endif
[[nodiscard]] std::vector<DeviceInfo> GetDevices() {
#ifdef WEBRTC_LINUX
auto options = GetVideoCaptureOptions();
const auto info = std::unique_ptr<
webrtc::VideoCaptureModule::DeviceInfo
>(webrtc::VideoCaptureFactory::CreateDeviceInfo(&options));
#else // WEBRTC_LINUX
const auto info = std::unique_ptr<
webrtc::VideoCaptureModule::DeviceInfo
>(webrtc::VideoCaptureFactory::CreateDeviceInfo());
#endif
auto result = std::vector<DeviceInfo>();
if (!info) {
return result;
}
const auto count = info->NumberOfDevices();
for (auto i = uint32_t(); i != count; ++i) {
constexpr auto kLengthLimit = 256;
auto id = std::string(kLengthLimit, char(0));
auto name = std::string(kLengthLimit, char(0));
info->GetDeviceName(
i,
name.data(),
name.size(),
id.data(),
id.size());
const auto utfName = QString::fromUtf8(name.c_str());
const auto utfId = id[0] ? QString::fromUtf8(id.c_str()) : utfName;
result.push_back({
.id = utfId,
.name = utfName,
.type = DeviceType::Camera,
});
}
return result;
}
} // namespace
EnvironmentVideoCapture::EnvironmentVideoCapture(
not_null<EnvironmentDelegate*> delegate)
: _delegate(delegate) {
}
EnvironmentVideoCapture::~EnvironmentVideoCapture() {
}
QString EnvironmentVideoCapture::defaultId(DeviceType type) {
Expects(type == DeviceType::Camera);
return DefaultId();
}
DeviceInfo EnvironmentVideoCapture::device(
DeviceType type,
const QString &id) {
Expects(type == DeviceType::Camera);
const auto devices = GetDevices();
const auto i = ranges::find(devices, id, &DeviceInfo::id);
return (i != end(devices)) ? *i : DeviceInfo();
}
std::vector<DeviceInfo> EnvironmentVideoCapture::devices(DeviceType type) {
Expects(type == DeviceType::Camera);
return GetDevices();
}
bool EnvironmentVideoCapture::refreshFullListOnChange(DeviceType type) {
Expects(type == DeviceType::Camera);
return true;
}
bool EnvironmentVideoCapture::desktopCaptureAllowed() const {
Unexpected("EnvironmentVideoCapture::desktopCaptureAllowed.");
}
auto EnvironmentVideoCapture::uniqueDesktopCaptureSource() const
-> std::optional<QString> {
Unexpected("EnvironmentVideoCapture::uniqueDesktopCaptureSource.");
}
void EnvironmentVideoCapture::defaultIdRequested(DeviceType type) {
_delegate->devicesForceRefresh(type);
}
void EnvironmentVideoCapture::devicesRequested(DeviceType type) {
_delegate->devicesForceRefresh(type);
}
QString EnvironmentVideoCapture::DefaultId() {
const auto devices = GetDevices();
return devices.empty() ? QString() : devices.front().id;
}
} // namespace Webrtc::details

View File

@@ -0,0 +1,40 @@
// 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"
namespace Webrtc::details {
class EnvironmentVideoCapture final : public Platform::Environment {
public:
using EnvironmentDelegate = Platform::EnvironmentDelegate;
explicit EnvironmentVideoCapture(
not_null<EnvironmentDelegate*> delegate);
~EnvironmentVideoCapture();
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;
[[nodiscard]] static QString DefaultId();
private:
const not_null<EnvironmentDelegate*> _delegate;
};
} // namespace Webrtc::details

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
// 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 <modules/audio_device/include/audio_device.h>
#include <modules/audio_device/audio_device_buffer.h>
#include <crl/crl_time.h>
#include <al.h>
#include <alc.h>
#include <atomic>
#include <QtCore/QMutex>
namespace rtc {
class Thread;
} // namespace rtc
namespace Webrtc::details {
struct DeviceResolvedIds {
QMutex mutex;
DeviceResolvedId playback{ .type = DeviceType::Playback };
DeviceResolvedId capture{ .type = DeviceType::Capture };
};
class AudioDeviceOpenAL : public webrtc::AudioDeviceModule {
public:
explicit AudioDeviceOpenAL(webrtc::TaskQueueFactory *taskQueueFactory);
~AudioDeviceOpenAL();
[[nodiscard]] Fn<void(DeviceResolvedId)> setDeviceIdCallback();
int32_t ActiveAudioLayer(AudioLayer *audioLayer) const override;
int32_t RegisterAudioCallback(
webrtc::AudioTransport *audioCallback) override;
// Main initialization 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:
struct Data;
struct ExactQueuedTime {
crl::time now = 0;
crl::time queued = 0;
};
template <typename Callback>
std::invoke_result_t<Callback> sync(Callback &&callback);
void openRecordingDevice();
void openPlayoutDevice();
void closeRecordingDevice();
// NB! stopPlayingOnThread should be called before this,
// to clear the thread local context and event callback.
void closePlayoutDevice();
int restartPlayout();
int restartRecording();
void restartRecordingQueued();
void restartPlayoutQueued();
void ensureThreadStarted();
void startCaptureOnThread();
void stopCaptureOnThread();
void startPlayingOnThread();
// NB! closePlayoutDevice should be called after this, so that next time
// we start playing, we set the thread local context and event callback.
void stopPlayingOnThread();
void processData();
void processRecordingData();
void processPlayoutData();
bool processRecordedPart(bool firstInCycle);
void clearProcessedBuffers();
bool clearProcessedBuffer();
void unqueueAllBuffers();
void handleEvent(
ALenum eventType,
ALuint object,
ALuint param,
ALsizei length,
const ALchar *message);
[[nodiscard]] crl::time countExactQueuedMsForLatency(
crl::time now,
bool playing);
[[nodiscard]] crl::time queryRecordingLatencyMs();
rtc::Thread *_thread = nullptr;
webrtc::AudioDeviceBuffer _audioDeviceBuffer;
std::unique_ptr<Data> _data;
std::shared_ptr<DeviceResolvedIds> _deviceResolvedIds;
ALCdevice *_playoutDevice = nullptr;
ALCcontext *_playoutContext = nullptr;
crl::time _playoutLatency = 0;
int _playoutChannels = 2;
bool _playoutInitialized = false;
bool _playoutFailed = false;
ALCdevice *_recordingDevice = nullptr;
crl::time _recordingLatency = 0;
bool _recordingInitialized = false;
bool _recordingFailed = false;
bool _speakerInitialized = false;
bool _microphoneInitialized = false;
bool _initialized = false;
};
} // namespace Webrtc::details

View File

@@ -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

View File

@@ -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

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

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -0,0 +1,195 @@
// 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/webrtc_audio_input_tester.h"
#include "webrtc/webrtc_device_common.h"
#include "webrtc/webrtc_create_adm.h"
#include "crl/crl_object_on_thread.h"
#include "crl/crl_async.h"
#include <api/task_queue/default_task_queue_factory.h>
#include <modules/audio_device/include/audio_device_defines.h>
#include <modules/audio_device/include/audio_device.h>
namespace Webrtc {
class AudioInputTester::Impl : public webrtc::AudioTransport {
public:
explicit Impl(const std::shared_ptr<std::atomic<int>> &maxSample);
~Impl();
void setDeviceId(const DeviceResolvedId &deviceId);
private:
void init();
void restart();
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;
// 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;
// Method to pull mixed render audio data from all active VoE channels.
// The data will not be passed as reference for audio processing internally.
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;
std::shared_ptr<std::atomic<int>> _maxSample;
std::unique_ptr<webrtc::TaskQueueFactory> _taskQueueFactory;
rtc::scoped_refptr<webrtc::AudioDeviceModule> _adm;
Fn<void(DeviceResolvedId)> _setDeviceIdCallback;
DeviceResolvedId _deviceId;
};
AudioInputTester::Impl::Impl(
const std::shared_ptr<std::atomic<int>> &maxSample)
: _maxSample(std::move(maxSample))
, _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory())
, _deviceId{ .type = DeviceType::Capture } {
const auto saveSetDeviceIdCallback = [=](
Fn<void(DeviceResolvedId)> setDeviceIdCallback) {
_setDeviceIdCallback = std::move(setDeviceIdCallback);
if (!_deviceId.isDefault()) {
_setDeviceIdCallback(_deviceId);
restart();
}
};
_adm = CreateAudioDeviceModule(
_taskQueueFactory.get(),
saveSetDeviceIdCallback);
init();
}
AudioInputTester::Impl::~Impl() {
if (_adm) {
_adm->StopRecording();
_adm->RegisterAudioCallback(nullptr);
_adm->Terminate();
}
}
void AudioInputTester::Impl::init() {
if (!_adm) {
return;
}
_adm->Init();
_adm->RegisterAudioCallback(this);
}
void AudioInputTester::Impl::setDeviceId(const DeviceResolvedId &deviceId) {
_deviceId = deviceId;
if (_setDeviceIdCallback) {
_setDeviceIdCallback(_deviceId);
restart();
}
}
void AudioInputTester::Impl::restart() {
if (!_adm) {
return;
}
_adm->StopRecording();
_adm->SetRecordingDevice(0);
if (_adm->InitRecording() == 0) {
_adm->StartRecording();
}
}
int32_t AudioInputTester::Impl::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) {
const auto channels = nBytesPerSample / sizeof(int16_t);
if (channels > 0 && !(nBytesPerSample % sizeof(int16_t))) {
auto max = 0;
auto values = static_cast<const int16_t*>(audioSamples);
for (auto i = size_t(); i != nSamples * channels; ++i) {
if (max < values[i]) {
max = values[i];
}
}
const auto now = _maxSample->load();
if (max > now) {
_maxSample->store(max);
}
}
newMicLevel = currentMicLevel;
return 0;
}
int32_t AudioInputTester::Impl::NeedMorePlayData(const size_t nSamples,
const size_t nBytesPerSample,
const size_t nChannels,
const uint32_t samplesPerSec,
void* audioSamples,
size_t& nSamplesOut,
int64_t* elapsed_time_ms,
int64_t* ntp_time_ms) {
nSamplesOut = 0;
return 0;
}
void AudioInputTester::Impl::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) {
}
AudioInputTester::AudioInputTester(rpl::producer<DeviceResolvedId> deviceId)
: _maxSample(std::make_shared<std::atomic<int>>(0))
, _impl(std::as_const(_maxSample)) {
std::move(
deviceId
) | rpl::on_next([=](const DeviceResolvedId &id) {
_impl.with([=](Impl &impl) {
impl.setDeviceId(id);
});
}, _lifetime);
}
AudioInputTester::~AudioInputTester() = default;
float AudioInputTester::getAndResetLevel() {
return _maxSample->exchange(0) / float(INT16_MAX);
}
} // namespace Webrtc

View File

@@ -0,0 +1,31 @@
// 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 <crl/crl_object_on_thread.h>
namespace Webrtc {
class AudioInputTester {
public:
explicit AudioInputTester(rpl::producer<DeviceResolvedId> deviceId);
~AudioInputTester();
[[nodiscard]] float getAndResetLevel();
private:
class Impl;
std::shared_ptr<std::atomic<int>> _maxSample;
crl::object_on_thread<Impl> _impl;
rpl::lifetime _lifetime;
};
} // namespace Webrtc

View File

@@ -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
//
#include "webrtc/webrtc_create_adm.h"
#include "webrtc/details/webrtc_openal_adm.h"
#include <api/make_ref_counted.h>
#include <modules/audio_device/include/audio_device_factory.h>
#ifdef WEBRTC_WIN
#include "webrtc/platform/win/webrtc_loopback_adm_win.h"
#endif // WEBRTC_WIN
namespace Webrtc {
rtc::scoped_refptr<webrtc::AudioDeviceModule> CreateAudioDeviceModule(
webrtc::TaskQueueFactory *factory,
Fn<void(Fn<void(DeviceResolvedId)>)> saveSetDeviceIdCallback) {
auto result = rtc::make_ref_counted<details::AudioDeviceOpenAL>(factory);
if (!result || result->Init() != 0) {
return nullptr;
}
saveSetDeviceIdCallback(result->setDeviceIdCallback());
return result;
}
auto AudioDeviceModuleCreator(
Fn<void(Fn<void(DeviceResolvedId)>)> saveSetDeviceIdCallback)
-> std::function<AudioDeviceModulePtr(webrtc::TaskQueueFactory*)> {
return [=](webrtc::TaskQueueFactory *factory) {
return CreateAudioDeviceModule(factory, saveSetDeviceIdCallback);
};
}
AudioDeviceModulePtr CreateLoopbackAudioDeviceModule(
webrtc::TaskQueueFactory* factory) {
#ifdef WEBRTC_WIN
auto result = rtc::make_ref_counted<details::AudioDeviceLoopbackWin>(
factory);
if (result->Init() == 0) {
return result;
}
#endif // WEBRTC_WIN
return nullptr;
}
auto LoopbackAudioDeviceModuleCreator()
-> std::function<AudioDeviceModulePtr(webrtc::TaskQueueFactory*)> {
return CreateLoopbackAudioDeviceModule;
}
} // namespace Webrtc

View File

@@ -0,0 +1,42 @@
// 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 <functional>
namespace webrtc {
class AudioDeviceModule;
class TaskQueueFactory;
template <class T>
class scoped_refptr;
} // namespace webrtc
namespace rtc {
template <typename T>
using scoped_refptr = webrtc::scoped_refptr<T>;
} // namespace rtc
namespace Webrtc {
struct DeviceResolvedId;
using AudioDeviceModulePtr = rtc::scoped_refptr<webrtc::AudioDeviceModule>;
AudioDeviceModulePtr CreateAudioDeviceModule(
webrtc::TaskQueueFactory* factory,
Fn<void(Fn<void(DeviceResolvedId)>)> saveSetDeviceIdCallback);
auto AudioDeviceModuleCreator(
Fn<void(Fn<void(DeviceResolvedId)>)> saveSetDeviceIdCallback)
-> std::function<AudioDeviceModulePtr(webrtc::TaskQueueFactory*)>;
AudioDeviceModulePtr CreateLoopbackAudioDeviceModule(
webrtc::TaskQueueFactory* factory);
auto LoopbackAudioDeviceModuleCreator()
-> std::function<AudioDeviceModulePtr(webrtc::TaskQueueFactory*)>;
} // namespace Webrtc

View File

@@ -0,0 +1,99 @@
// 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/qt/qt_compare.h"
#include <QString>
namespace Webrtc {
enum class DeviceType : uchar {
Playback,
Capture,
Camera,
};
enum class DeviceStateChange : uchar {
Active,
Inactive,
Disconnected,
};
enum class DeviceChangeReason : uchar {
Manual,
Connected,
Disconnected,
};
struct DeviceInfo {
QString id;
QString name;
DeviceType type = DeviceType::Playback;
bool inactive = false;
explicit operator bool() const {
return !id.isEmpty();
}
friend inline bool operator==(
const DeviceInfo &a,
const DeviceInfo &b) = default;
};
struct DeviceChange {
QString wasId;
QString nowId;
DeviceChangeReason reason = DeviceChangeReason::Manual;
explicit operator bool() const {
return wasId != nowId;
}
friend inline bool operator==(
const DeviceChange &a,
const DeviceChange &b) = default;
};
struct DevicesChange {
DeviceChange defaultChange;
std::vector<DeviceInfo> nowList;
};
inline QString kDefaultDeviceId = u"default"_q;
struct DeviceResolvedId {
QString value;
DeviceType type = DeviceType::Playback;
bool computedFromDefault = false;
[[nodiscard]] bool isDefault() const {
return computedFromDefault
|| value.isEmpty()
|| value == kDefaultDeviceId;
}
friend inline auto operator<=>(
const DeviceResolvedId &a,
const DeviceResolvedId &b) = default;
friend inline bool operator==(
const DeviceResolvedId &a,
const DeviceResolvedId &b) = default;
};
class CaptureMuteTracker {
public:
virtual void captureMuteChanged(bool muted) = 0;
[[nodiscard]] virtual auto captureMuteDeviceId()
-> rpl::producer<DeviceResolvedId> = 0;
};
enum class RecordAvailability : uchar {
None,
Audio,
VideoAndAudio,
};
} // namespace Webrtc

View File

@@ -0,0 +1,136 @@
// 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/webrtc_device_resolver.h"
#include "webrtc/webrtc_environment.h"
namespace Webrtc {
namespace {
[[nodiscard]] bool IsDefault(const QString &id) {
return id.isEmpty() || id == kDefaultDeviceId;
}
} // namespace
DeviceResolver::DeviceResolver(
not_null<Environment*> environment,
DeviceType type,
rpl::producer<QString> savedId)
: _environment(environment)
, _current{ .type = type }
, _data(_current) {
_data.changes() | rpl::on_next([=](DeviceResolvedId id) {
QMutexLocker lock(&_mutex);
_current = id;
}, _lifetime);
std::move(
savedId
) | rpl::on_next([=](QString id) {
QMutexLocker lock(&_mutex);
_savedId = id;
lock.unlock();
trackSavedId();
}, _lifetime);
}
void DeviceResolver::trackSavedId() {
const auto now = _environment->defaultId(_current.type);
if (IsDefault(_savedId)) {
_data = rpl::single(
DeviceChange{ now, now }
) | rpl::then(
_environment->defaultChanges(_current.type)
) | rpl::map([=](const DeviceChange &change) {
_lastChangeReason = change.reason;
return DeviceResolvedId{ change.nowId, _current.type, true };
});
return;
}
_data = rpl::single(DevicesChange{
DeviceChange{ now, now },
_environment->devices(_current.type),
}) | rpl::then(
_environment->changes(_current.type)
) | rpl::map([=](const DevicesChange &change) {
const auto now = _data.current();
const auto i = ranges::find(
change.nowList,
_savedId,
&DeviceInfo::id);
if (i != end(change.nowList) && !i->inactive) {
auto result = DeviceResolvedId{ _savedId, now.type };
if (now != result) {
_lastChangeReason = DeviceChangeReason::Connected;
}
return result;
} else {
_lastChangeReason = (now == DeviceResolvedId{ _savedId })
? DeviceChangeReason::Disconnected
: change.defaultChange.reason;
const auto &defaultId = change.defaultChange.nowId;
return DeviceResolvedId{ defaultId, now.type, true };
}
});
}
DeviceResolvedId DeviceResolver::current() const {
if (IsDefault(_savedId)) {
_environment->validateDefaultId(_current.type);
} else {
_environment->validateDevices(_current.type);
}
return _data.current();
}
DeviceResolvedId DeviceResolver::threadSafeCurrent() const {
QMutexLocker lock(&_mutex);
auto savedId = _savedId;
auto current = _current;
lock.unlock();
return _environment->threadSafeResolveId(current, savedId);
}
rpl::producer<DeviceResolvedId> DeviceResolver::value() const {
return _data.value();
}
rpl::producer<DeviceResolvedId> DeviceResolver::changes() const {
return _data.changes();
}
DeviceChangeReason DeviceResolver::lastChangeReason() const {
return _lastChangeReason;
}
rpl::producer<QString> DeviceIdOrDefault(
rpl::producer<QString> id) {
return std::move(id) | rpl::map([](const QString &id) {
return !id.isEmpty() ? id : kDefaultDeviceId;
}) | rpl::distinct_until_changed();
}
rpl::producer<QString> DeviceIdValueWithFallback(
rpl::producer<QString> id,
rpl::producer<QString> fallback) {
return rpl::combine(
std::move(id),
std::move(fallback)
) | rpl::map([](const QString &id, const QString &fallback) {
return !id.isEmpty()
? id
: !fallback.isEmpty()
? fallback
: kDefaultDeviceId;
}) | rpl::distinct_until_changed();
}
} // namespace Webrtc

View File

@@ -0,0 +1,53 @@
// 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/qt/qt_compare.h"
#include "webrtc/webrtc_device_common.h"
#include <QtCore/QMutex>
namespace Webrtc {
class Environment;
class DeviceResolver final {
public:
DeviceResolver(
not_null<Environment*> environment,
DeviceType type,
rpl::producer<QString> savedValue);
[[nodiscard]] DeviceResolvedId current() const;
[[nodiscard]] rpl::producer<DeviceResolvedId> value() const;
[[nodiscard]] rpl::producer<DeviceResolvedId> changes() const;
[[nodiscard]] DeviceResolvedId threadSafeCurrent() const;
[[nodiscard]] DeviceChangeReason lastChangeReason() const;
private:
void trackSavedId();
const not_null<Environment*> _environment;
QString _savedId;
DeviceResolvedId _current;
mutable QMutex _mutex;
rpl::variable<DeviceResolvedId> _data;
DeviceChangeReason _lastChangeReason = DeviceChangeReason::Manual;
rpl::lifetime _lifetime;
};
[[nodiscard]] rpl::producer<QString> DeviceIdOrDefault(
rpl::producer<QString> id);
[[nodiscard]] rpl::producer<QString> DeviceIdValueWithFallback(
rpl::producer<QString> id,
rpl::producer<QString> fallback);
} // namespace Webrtc

View File

@@ -0,0 +1,396 @@
// 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/webrtc_environment.h"
#include "base/debug_log.h"
#include "webrtc/platform/webrtc_platform_environment.h"
#include "webrtc/details/webrtc_environment_openal.h"
#include "webrtc/details/webrtc_environment_video_capture.h"
#ifdef WEBRTC_MAC
#include "webrtc/platform/mac/webrtc_environment_mac.h"
#endif // WEBRTC_MAC
#include <crl/crl_async.h>
namespace Webrtc {
namespace {
[[nodiscard]] QString SerializeDevices(const std::vector<DeviceInfo> &list) {
auto result = QStringList();
for (const auto &device : list) {
result.push_back('"' + device.name + "\" <" + device.id + ">");
}
return "{ " + result.join(", ") + " }";
}
[[nodiscard]] QString TypeToString(DeviceType type) {
switch (type) {
case DeviceType::Playback: return "Playback";
case DeviceType::Capture: return "Capture";
case DeviceType::Camera: return "Camera";
}
Unexpected("Type in TypeToString.");
}
} // namespace
Environment::Environment()
: _platform(
Platform::CreateEnvironment((Platform::EnvironmentDelegate*)this))
, _devices{
resolveDevices(DeviceType::Playback),
resolveDevices(DeviceType::Capture),
resolveDevices(DeviceType::Camera),
} {
using Type = DeviceType;
for (const auto type : { Type::Playback, Type::Capture, Type::Camera }) {
if (synced(type)) {
logState(type, LogType::Initial);
} else {
logSyncError(type);
}
}
}
Environment::~Environment() = default;
int Environment::TypeToIndex(DeviceType type) {
const auto result = int(type);
Ensures(result >= 0 && result < kTypeCount);
return result;
}
Environment::Devices Environment::resolveDevices(DeviceType type) const {
return {
.defaultId = _platform->defaultId(type),
.list = _platform->devices(type),
.refreshFullListOnChange = _platform->refreshFullListOnChange(type),
};
}
QString Environment::defaultId(DeviceType type) const {
validateDefaultId(type);
return _devices[TypeToIndex(type)].defaultId;
}
std::vector<DeviceInfo> Environment::devices(DeviceType type) const {
validateDevices(type);
return _devices[TypeToIndex(type)].list;
}
rpl::producer<DevicesChange> Environment::changes(
DeviceType type) const {
const auto devices = &_devices[TypeToIndex(type)];
return devices->changes.events(
) | rpl::map([=](DevicesChangeEvent &&event) -> DevicesChange {
return { std::move(event.defaultChange), devices->list };
});
}
rpl::producer<DeviceChange> Environment::defaultChanges(
DeviceType type) const {
return _devices[TypeToIndex(type)].changes.events(
) | rpl::filter([](const DevicesChangeEvent &event) {
return !!event.defaultChange;
}) | rpl::map([](DevicesChangeEvent &&event) {
return std::move(event.defaultChange);
});
}
rpl::producer<std::vector<DeviceInfo>> Environment::devicesValue(
DeviceType type) const {
validateDevices(type);
const auto devices = &_devices[TypeToIndex(type)];
return devices->changes.events_starting_with(
DevicesChangeEvent{ .listChanged = true }
) | rpl::filter([](const DevicesChangeEvent &event) {
return event.listChanged;
}) | rpl::map([=] {
return devices->list;
});
}
void Environment::forceRefresh(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
devices.defaultChangeFrom = std::exchange(
devices.defaultId,
_platform->defaultId(type));
const auto &old = *devices.defaultChangeFrom;
const auto newIsInOldList = ranges::contains(
devices.list,
devices.defaultId,
&DeviceInfo::id);
refreshDevices(type);
const auto oldIsInNewList = ranges::contains(
devices.list,
old,
&DeviceInfo::id);
if (devices.defaultId != old) {
devices.defaultChangeReason = !oldIsInNewList
? DeviceChangeReason::Disconnected
: !newIsInOldList
? DeviceChangeReason::Connected
: DeviceChangeReason::Manual;
}
maybeNotify(type);
}
bool Environment::desktopCaptureAllowed() const {
return _platform->desktopCaptureAllowed();
}
std::optional<QString> Environment::uniqueDesktopCaptureSource() const {
return _platform->uniqueDesktopCaptureSource();
}
DeviceResolvedId Environment::threadSafeResolveId(
const DeviceResolvedId &lastResolvedId,
const QString &savedId) const {
return _platform->threadSafeResolveId(lastResolvedId, savedId);
}
void Environment::setCaptureMuted(bool muted) {
_platform->setCaptureMuted(muted);
}
void Environment::setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track) {
_platform->setCaptureMuteTracker(tracker, track);
}
RecordAvailability Environment::recordAvailability() const {
const_cast<Environment*>(this)->refreshRecordAvailability();
return _recordAvailability.current();
}
auto Environment::recordAvailabilityValue() const
->rpl::producer<RecordAvailability> {
const_cast<Environment*>(this)->refreshRecordAvailability();
return _recordAvailability.value();
}
void Environment::refreshRecordAvailability() {
if (_recordAvailabilityRefreshing) {
_recordAvailabilityRefreshPending = true;
return;
}
_recordAvailabilityRefreshing = true;
const auto strong = static_cast<base::has_weak_ptr*>(this);
const auto weak = base::make_weak(strong);
crl::async([weak] {
const auto type = DeviceType::Capture;
const auto audio = details::EnvironmentOpenAL::DefaultId(type);
#ifdef WEBRTC_MAC
const auto vtype = DeviceType::Camera;
const auto video = Platform::EnvironmentMac::DefaultId(vtype);
#else // WEBRTC_MAC
const auto video = details::EnvironmentVideoCapture::DefaultId();
#endif // WEBRTC_MAC
const auto availability = audio.isEmpty()
? RecordAvailability::None
: video.isEmpty()
? RecordAvailability::Audio
: RecordAvailability::VideoAndAudio;
crl::on_main([weak, availability] {
if (const auto strong = weak.get()) {
const auto that = static_cast<Environment*>(strong);
that->applyRecordAvailability(availability);
}
});
});
}
void Environment::applyRecordAvailability(RecordAvailability value) {
_recordAvailability = value;
_recordAvailabilityRefreshing = false;
if (base::take(_recordAvailabilityRefreshPending)) {
refreshRecordAvailability();
}
}
void Environment::defaultChanged(
DeviceType type,
DeviceChangeReason reason,
QString nowId) {
const auto guard = gsl::finally([&] {
validateAfterDefaultChange(type);
maybeNotify(type);
});
auto &devices = _devices[TypeToIndex(type)];
devices.defaultChangeFrom = std::exchange(
devices.defaultId,
std::move(nowId));
devices.defaultChangeReason = reason;
}
void Environment::deviceStateChanged(
DeviceType type,
QString id,
DeviceStateChange state) {
const auto guard = gsl::finally([&] {
validateAfterListChange(type);
maybeNotify(type);
});
auto &devices = _devices[TypeToIndex(type)];
if (devices.refreshFullListOnChange) {
refreshDevices(type);
}
const auto i = ranges::find(
devices.list,
id,
&DeviceInfo::id);
if (i == end(devices.list)) {
if (state == DeviceStateChange::Disconnected) {
return;
} else if (auto info = _platform->device(type, id)) {
devices.list.push_back(std::move(info));
devices.listChanged = true;
}
} else if (state == DeviceStateChange::Disconnected) {
const auto from = ranges::remove(
i,
end(devices.list),
id,
&DeviceInfo::id);
if (from != end(devices.list)) {
devices.list.erase(from, end(devices.list));
devices.listChanged = true;
}
} else if (i != end(devices.list)) {
const auto inactive = (state != DeviceStateChange::Active);
if (i->inactive != inactive) {
i->inactive = inactive;
devices.listChanged = true;
}
}
}
void Environment::devicesForceRefresh(DeviceType type) {
forceRefresh(type);
}
void Environment::validateDefaultId(DeviceType type) const {
_platform->defaultIdRequested(type);
}
void Environment::validateDevices(DeviceType type) const {
_platform->devicesRequested(type);
}
bool Environment::synced(DeviceType type) const {
const auto &devices = _devices[TypeToIndex(type)];
return ranges::contains(
devices.list,
devices.defaultId,
&DeviceInfo::id);
}
void Environment::validateAfterListChange(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
if (!devices.listChanged || synced(type)) {
return;
}
devices.defaultChangeFrom = std::exchange(
devices.defaultId,
_platform->defaultId(type));
devices.defaultChangeReason = DeviceChangeReason::Disconnected;
if (devices.defaultChangeFrom != devices.defaultId
&& synced(type)) {
return;
}
refreshDevices(type);
if (!devices.listChanged || !synced(type)) {
logSyncError(type);
}
}
void Environment::validateAfterDefaultChange(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
if (!devices.defaultChangeFrom
|| devices.defaultChangeFrom == devices.defaultId
|| synced(type)) {
return;
}
refreshDevices(type);
if (devices.listChanged && synced(type)) {
return;
}
auto changedOneMoreFromId = std::exchange(
devices.defaultId,
_platform->defaultId(type));
devices.defaultChangeReason = DeviceChangeReason::Disconnected;
if (devices.defaultId == changedOneMoreFromId || !synced(type)) {
logSyncError(type);
}
}
void Environment::maybeNotify(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
if (devices.defaultChangeFrom == devices.defaultId) {
devices.defaultChangeFrom = std::nullopt;
}
if (!devices.listChanged && !devices.defaultChangeFrom) {
return;
}
const auto listChanged = base::take(devices.listChanged);
const auto from = base::take(devices.defaultChangeFrom);
const auto reason = base::take(devices.defaultChangeReason);
devices.changes.fire({
.defaultChange = {
.wasId = from.value_or(QString()),
.nowId = from ? devices.defaultId : QString(),
.reason = (from ? reason : DeviceChangeReason::Manual),
},
.listChanged = listChanged,
});
}
void Environment::logSyncError(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
LOG(("Media Error: "
"Can't sync default device for type %1, default: %2, list: %3"
).arg(TypeToString(type)
).arg(devices.defaultId
).arg(SerializeDevices(devices.list)));
}
void Environment::logState(DeviceType type, LogType log) {
auto &devices = _devices[TypeToIndex(type)];
auto phrase = u"Media Info: Type %1, default: %2, list: %3"_q
.arg(TypeToString(type))
.arg(devices.defaultId)
.arg(SerializeDevices(devices.list));
if (log == LogType::Initial) {
phrase += u", full list refresh: %1"_q.arg(
devices.refreshFullListOnChange ? "true" : "false");
}
switch (log) {
case LogType::Initial:
case LogType::Always:
LOG((phrase));
break;
case LogType::Debug:
DEBUG_LOG((phrase));
break;
}
}
void Environment::refreshDevices(DeviceType type) {
auto &devices = _devices[TypeToIndex(type)];
auto list = _platform->devices(type);
if (devices.list != list) {
devices.list = std::move(list);
devices.listChanged = true;
}
}
} // namespace Webrtc

View File

@@ -0,0 +1,136 @@
// 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/weak_ptr.h"
#include "webrtc/webrtc_device_common.h"
#include <rpl/producer.h>
#include <optional>
namespace Webrtc::Platform {
class Environment;
class EnvironmentDelegate {
public:
virtual void defaultChanged(
DeviceType type,
DeviceChangeReason reason,
QString nowId) = 0;
virtual void deviceStateChanged(
DeviceType type,
QString id,
DeviceStateChange state) = 0;
virtual void devicesForceRefresh(DeviceType type) = 0;
};
} // namespace Webrtc::Platform
namespace Webrtc {
struct DeviceId;
class Environment final
: private Platform::EnvironmentDelegate
, private base::has_weak_ptr {
public:
Environment();
~Environment();
void forceRefresh(DeviceType type);
[[nodiscard]] QString defaultId(DeviceType type) const;
[[nodiscard]] std::vector<DeviceInfo> devices(DeviceType type) const;
[[nodiscard]] rpl::producer<DevicesChange> changes(
DeviceType type) const;
[[nodiscard]] rpl::producer<DeviceChange> defaultChanges(
DeviceType type) const;
[[nodiscard]] rpl::producer<std::vector<DeviceInfo>> devicesValue(
DeviceType type) const;
void validateDefaultId(DeviceType type) const;
void validateDevices(DeviceType type) const;
[[nodiscard]] bool desktopCaptureAllowed() const;
[[nodiscard]] std::optional<QString> uniqueDesktopCaptureSource() const;
[[nodiscard]] DeviceResolvedId threadSafeResolveId(
const DeviceResolvedId &lastResolvedId,
const QString &savedId) const;
void setCaptureMuted(bool muted);
void setCaptureMuteTracker(
not_null<CaptureMuteTracker*> tracker,
bool track);
[[nodiscard]] RecordAvailability recordAvailability() const;
[[nodiscard]] auto recordAvailabilityValue() const
-> rpl::producer<RecordAvailability>;
void refreshRecordAvailability();
private:
static constexpr auto kTypeCount = 3;
enum class LogType : uchar {
Initial,
Always,
Debug,
};
struct DevicesChangeEvent {
DeviceChange defaultChange;
bool listChanged = false;
};
struct Devices {
QString defaultId;
rpl::event_stream<DevicesChangeEvent> changes;
std::vector<DeviceInfo> list;
rpl::event_stream<> listChanges;
std::optional<QString> defaultChangeFrom;
DeviceChangeReason defaultChangeReason = DeviceChangeReason::Manual;
bool refreshFullListOnChange = false;
bool listChanged = false;
};
[[nodiscard]] static int TypeToIndex(DeviceType type);
[[nodiscard]] Devices resolveDevices(DeviceType type) const;
[[nodiscard]] bool synced(DeviceType type) const;
void validateAfterDefaultChange(DeviceType type);
void validateAfterListChange(DeviceType type);
void refreshDevices(DeviceType type);
void maybeNotify(DeviceType type);
void logSyncError(DeviceType type);
void logState(DeviceType type, LogType log);
void defaultChanged(
DeviceType type,
DeviceChangeReason reason,
QString nowId) override;
void deviceStateChanged(
DeviceType type,
QString id,
DeviceStateChange state) override;
void devicesForceRefresh(DeviceType type) override;
void applyRecordAvailability(RecordAvailability value);
const std::unique_ptr<Platform::Environment> _platform;
std::array<Devices, kTypeCount> _devices;
rpl::variable<RecordAvailability> _recordAvailability;
bool _recordAvailabilityRefreshing = false;
bool _recordAvailabilityRefreshPending = false;
rpl::lifetime _lifetime;
};
} // namespace Webrtc

View File

@@ -0,0 +1,18 @@
// 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 <QtCore/QString>
#include <rpl/rpl.h>
#include <range/v3/all.hpp>
#include <crl/crl_time.h>
#include <crl/crl_on_main.h>
#include "base/algorithm.h"
#include "base/basic_types.h"
#include "base/flat_map.h"
#include "base/flat_set.h"

View File

@@ -0,0 +1,579 @@
// 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/webrtc_video_track.h"
#include "ffmpeg/ffmpeg_utility.h"
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <api/video/video_sink_interface.h>
#include <api/video/video_frame.h>
namespace Webrtc {
namespace {
constexpr auto kDropFramesWhileInactive = 5 * crl::time(1000);
[[nodiscard]] bool GoodForRequest(
const QImage &image,
int rotation,
const FrameRequest &request) {
if (request.resize.isEmpty()) {
return true;
} else if (rotation != 0) {
return false;
//} else if ((request.radius != ImageRoundRadius::None)
// && ((request.corners & RectPart::AllCorners) != 0)) {
// return false;
}
return (request.resize == request.outer)
&& (request.resize == image.size());
}
void PaintFrameOuter(QPainter &p, const QRect &inner, QSize outer) {
const auto left = inner.x();
const auto right = outer.width() - inner.width() - left;
const auto top = inner.y();
const auto bottom = outer.height() - inner.height() - top;
if (left > 0) {
p.fillRect(0, 0, left, outer.height(), Qt::black);
}
if (right > 0) {
p.fillRect(
outer.width() - right,
0,
right,
outer.height(),
Qt::black);
}
if (top > 0) {
p.fillRect(left, 0, inner.width(), top, Qt::black);
}
if (bottom > 0) {
p.fillRect(
left,
outer.height() - bottom,
inner.width(),
bottom,
Qt::black);
}
}
void PaintFrameInner(
QPainter &p,
QRect to,
const QImage &original,
bool alpha,
int rotation) {
const auto rotated = [](QRect rect, int rotation) {
switch (rotation) {
case 0: return rect;
case 90: return QRect(
rect.y(),
-rect.x() - rect.width(),
rect.height(),
rect.width());
case 180: return QRect(
-rect.x() - rect.width(),
-rect.y() - rect.height(),
rect.width(),
rect.height());
case 270: return QRect(
-rect.y() - rect.height(),
rect.x(),
rect.height(),
rect.width());
}
Unexpected("Rotation in PaintFrameInner.");
};
const auto hints = {
QPainter::Antialiasing,
QPainter::SmoothPixmapTransform,
QPainter::TextAntialiasing
};
for (const auto hint : hints) {
p.setRenderHint(hint);
}
if (rotation) {
p.rotate(rotation);
}
const auto rect = rotated(to, rotation);
if (alpha) {
p.fillRect(rect, Qt::white);
}
p.drawImage(rect, original);
}
void PaintFrameContent(
QPainter &p,
const QImage &original,
bool alpha,
int rotation,
const FrameRequest &request) {
const auto full = request.outer.isEmpty()
? original.size()
: request.outer;
const auto size = request.resize.isEmpty()
? original.size()
: request.resize;
const auto to = QRect(
(full.width() - size.width()) / 2,
(full.height() - size.height()) / 2,
size.width(),
size.height());
PaintFrameOuter(p, to, full);
PaintFrameInner(p, to, original, alpha, rotation);
}
void ApplyFrameRounding(QImage &storage, const FrameRequest &request) {
//if (!(request.corners & RectPart::AllCorners)
// || (request.radius == ImageRoundRadius::None)) {
// return;
//}
//Images::prepareRound(storage, request.radius, request.corners);
}
QImage PrepareByRequest(
const QImage &original,
bool alpha,
int rotation,
const FrameRequest &request,
QImage storage) {
Expects(!request.outer.isEmpty() || alpha);
const auto outer = request.outer.isEmpty()
? original.size()
: request.outer;
if (!FFmpeg::GoodStorageForFrame(storage, outer)) {
storage = FFmpeg::CreateFrameStorage(outer);
}
QPainter p(&storage);
PaintFrameContent(p, original, alpha, rotation, request);
p.end();
ApplyFrameRounding(storage, request);
return storage;
}
} // namespace
struct VideoTrack::Frame {
int64 mcstimestamp = 0;
QImage original;
QImage prepared;
rtc::scoped_refptr<webrtc::I420BufferInterface> native;
FrameYUV420 yuv420;
FrameRequest request = FrameRequest::NonStrict();
FrameFormat format = FrameFormat::None;
int rotation = 0;
bool displayed = false;
bool alpha = false;
bool requireARGB32 = true;
};
class VideoTrack::Sink final
: public rtc::VideoSinkInterface<webrtc::VideoFrame>
, public std::enable_shared_from_this<Sink> {
public:
explicit Sink(bool requireARGB32);
using PrepareFrame = not_null<Frame*>;
using PrepareState = bool;
struct FrameWithIndex {
not_null<Frame*> frame;
int index = -1;
};
[[nodiscard]] bool firstPresentHappened() const;
// Called from the main thread.
void markFrameShown();
[[nodiscard]] not_null<Frame*> frameForPaint();
[[nodiscard]] FrameWithIndex frameForPaintWithIndex();
[[nodiscard]] rpl::producer<> renderNextFrameOnMain() const;
void destroyFrameForPaint();
void OnFrame(const webrtc::VideoFrame &nativeVideoFrame) override;
private:
struct FrameForDecode {
not_null<Frame*> frame;
int counter = 0;
};
[[nodiscard]] FrameForDecode nextFrameForDecode();
void presentNextFrame(const FrameForDecode &frame);
[[nodiscard]] not_null<Frame*> getFrame(int index);
[[nodiscard]] not_null<const Frame*> getFrame(int index) const;
[[nodiscard]] int counter() const;
bool decodeFrame(
const webrtc::VideoFrame &nativeVideoFrame,
not_null<Frame*> frame);
void notifyFrameDecoded();
FFmpeg::SwscalePointer _decodeContext;
std::atomic<int> _counter = 0;
// Main thread.
int _counterCycle = 0;
static constexpr auto kFramesCount = 3;
std::array<Frame, kFramesCount> _frames;
rpl::event_stream<> _renderNextFrameOnMain;
};
VideoTrack::Sink::Sink(bool requireARGB32) {
for (auto &frame : _frames) {
frame.requireARGB32 = requireARGB32;
}
}
void VideoTrack::Sink::OnFrame(const webrtc::VideoFrame &nativeVideoFrame) {
const auto decode = nextFrameForDecode();
if (decodeFrame(nativeVideoFrame, decode.frame)) {
PrepareFrameByRequests(decode.frame, nativeVideoFrame.rotation());
presentNextFrame(decode);
}
}
auto VideoTrack::Sink::nextFrameForDecode() -> FrameForDecode {
const auto current = counter();
const auto index = ((current + 3) / 2) % kFramesCount;
const auto frame = getFrame(index);
return { frame, current };
}
void VideoTrack::Sink::presentNextFrame(const FrameForDecode &frame) {
// Release this frame to the main thread for rendering.
const auto present = [&](int counter) {
Expects(counter + 1 < 2 * kFramesCount);
_counter.store(counter + 1, std::memory_order_release);
notifyFrameDecoded();
};
switch (frame.counter) {
case 0: present(0);
case 1: return;
case 2: present(2);
case 3: return;
case 4: present(4);
case 5: return;
//case 6: present(6);
//case 7: return;
}
Unexpected("Counter value in VideoTrack::Sink::presentNextFrame.");
}
bool VideoTrack::Sink::decodeFrame(
const webrtc::VideoFrame &nativeVideoFrame,
not_null<Frame*> frame) {
const auto native = nativeVideoFrame.video_frame_buffer()->ToI420();
const auto size = QSize{ native->width(), native->height() };
if (size.isEmpty()) {
frame->format = FrameFormat::None;
return false;
}
frame->mcstimestamp = nativeVideoFrame.timestamp_us();
if (!frame->mcstimestamp) {
frame->mcstimestamp = crl::now() * 1000;
}
if (!frame->requireARGB32) {
if (!frame->original.isNull()) {
frame->original = frame->prepared = QImage();
}
frame->format = FrameFormat::YUV420;
frame->native = native;
frame->yuv420 = FrameYUV420{
.size = size,
.chromaSize = { native->ChromaWidth(), native->ChromaHeight() },
.y = { native->DataY(), native->StrideY() },
.u = { native->DataU(), native->StrideU() },
.v = { native->DataV(), native->StrideV() },
};
return true;
}
frame->format = FrameFormat::ARGB32;
frame->yuv420 = FrameYUV420{
.size = size,
};
if (!FFmpeg::GoodStorageForFrame(frame->original, size)) {
frame->original = FFmpeg::CreateFrameStorage(size);
}
_decodeContext = FFmpeg::MakeSwscalePointer(
size,
AV_PIX_FMT_YUV420P,
size,
AV_PIX_FMT_BGRA,
&_decodeContext);
Assert(_decodeContext != nullptr);
// AV_NUM_DATA_POINTERS defined in AVFrame struct
const uint8_t *src[AV_NUM_DATA_POINTERS] = {
native->DataY(),
native->DataU(),
native->DataV(),
nullptr
};
int srcLineSize[AV_NUM_DATA_POINTERS] = {
native->StrideY(),
native->StrideU(),
native->StrideV(),
0
};
uint8_t *dst[AV_NUM_DATA_POINTERS] = { frame->original.bits(), nullptr };
int dstLineSize[AV_NUM_DATA_POINTERS] = { int(frame->original.bytesPerLine()), 0 };
sws_scale(
_decodeContext.get(),
src,
srcLineSize,
0,
frame->original.height(),
dst,
dstLineSize);
return true;
}
void VideoTrack::Sink::notifyFrameDecoded() {
crl::on_main([weak = weak_from_this()] {
if (const auto strong = weak.lock()) {
strong->_renderNextFrameOnMain.fire({});
}
});
}
int VideoTrack::Sink::counter() const {
return _counter.load(std::memory_order_acquire);
}
not_null<VideoTrack::Frame*> VideoTrack::Sink::getFrame(int index) {
Expects(index >= 0 && index < kFramesCount);
return &_frames[index];
}
not_null<const VideoTrack::Frame*> VideoTrack::Sink::getFrame(
int index) const {
Expects(index >= 0 && index < kFramesCount);
return &_frames[index];
}
// Sometimes main thread subscribes to check frame requests before
// the first frame is ready and presented and sometimes after.
bool VideoTrack::Sink::firstPresentHappened() const {
switch (counter()) {
case 0: return false;
case 1: return true;
}
Unexpected("Counter value in VideoTrack::Sink::firstPresentHappened.");
}
void VideoTrack::Sink::markFrameShown() {
const auto jump = [&](int counter) {
if (counter == 2 * kFramesCount - 1) {
++_counterCycle;
}
const auto next = (counter + 1) % (2 * kFramesCount);
const auto index = next / 2;
const auto frame = getFrame(index);
frame->displayed = true;
_counter.store(
next,
std::memory_order_release);
};
switch (counter()) {
case 0: return;
case 1: return jump(1);
case 2: return;
case 3: return jump(3);
case 4: return;
case 5: return jump(5);
//case 6: return;
//case 7: return jump(7);
}
Unexpected("Counter value in VideoTrack::Sink::markFrameShown.");
}
not_null<VideoTrack::Frame*> VideoTrack::Sink::frameForPaint() {
return frameForPaintWithIndex().frame;
}
VideoTrack::Sink::FrameWithIndex VideoTrack::Sink::frameForPaintWithIndex() {
const auto index = counter() / 2;
return {
.frame = getFrame(index),
.index = (_counterCycle * 2 * kFramesCount) + index,
};
}
void VideoTrack::Sink::destroyFrameForPaint() {
const auto frame = getFrame(counter() / 2);
if (!frame->original.isNull()) {
frame->original = frame->prepared = QImage();
}
if (frame->native) {
frame->native = nullptr;
}
frame->yuv420 = FrameYUV420();
frame->format = FrameFormat::None;
}
rpl::producer<> VideoTrack::Sink::renderNextFrameOnMain() const {
return _renderNextFrameOnMain.events();
}
VideoTrack::VideoTrack(VideoState state, bool requireARGB32)
: _state(state) {
_sink = std::make_shared<Sink>(requireARGB32);
}
VideoTrack::~VideoTrack() {
}
rpl::producer<> VideoTrack::renderNextFrame() const {
return rpl::merge(
_sink->renderNextFrameOnMain(),
_state.changes() | rpl::to_empty);
}
auto VideoTrack::sink()
-> std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> {
return _sink;
}
[[nodiscard]] VideoState VideoTrack::state() const {
return _state.current();
}
[[nodiscard]] rpl::producer<VideoState> VideoTrack::stateValue() const {
return _state.value();
}
[[nodiscard]] rpl::producer<VideoState> VideoTrack::stateChanges() const {
return _state.changes();
}
void VideoTrack::setState(VideoState state) {
if (state == VideoState::Inactive) {
_inactiveFrom = crl::now();
} else {
_inactiveFrom = 0;
}
_state = state;
if (state == VideoState::Inactive) {
// save last frame?..
_sink->destroyFrameForPaint();
}
}
void VideoTrack::markFrameShown() {
_sink->markFrameShown();
}
QImage VideoTrack::frame(const FrameRequest &request) {
if (_inactiveFrom > 0
&& (_inactiveFrom + kDropFramesWhileInactive > crl::now())) {
_sink->destroyFrameForPaint();
return {};
}
const auto frame = _sink->frameForPaint();
const auto preparedFor = frame->request;
const auto changed = !preparedFor.goodFor(request);
const auto useRequest = changed ? request : preparedFor;
if (changed) {
//_wrapped.with([=](Implementation &unwrapped) {
// unwrapped.updateFrameRequest(instance, useRequest);
//});
}
if (!frame->alpha
&& GoodForRequest(frame->original, frame->rotation, useRequest)) {
return frame->original;
} else if (changed || frame->prepared.isNull()) {
if (changed) {
frame->request = useRequest;
}
frame->prepared = PrepareByRequest(
frame->original,
frame->alpha,
frame->rotation,
useRequest,
std::move(frame->prepared));
}
return frame->prepared;
}
FrameWithInfo VideoTrack::frameWithInfo(bool requireARGB32) const {
if (_inactiveFrom > 0
&& (_inactiveFrom + kDropFramesWhileInactive > crl::now())) {
_sink->destroyFrameForPaint();
return {};
}
const auto data = _sink->frameForPaintWithIndex();
Assert(!requireARGB32
|| (data.frame->format == FrameFormat::ARGB32)
|| (data.frame->format == FrameFormat::None));
if (data.frame->requireARGB32 && !requireARGB32) {
data.frame->requireARGB32 = requireARGB32;
}
return {
.mcstimestamp = data.frame->mcstimestamp,
.original = data.frame->original,
.yuv420 = &data.frame->yuv420,
.format = data.frame->format,
.rotation = data.frame->rotation,
.index = data.index,
};
}
QSize VideoTrack::frameSize() const {
if (_inactiveFrom > 0
&& (_inactiveFrom + kDropFramesWhileInactive > crl::now())) {
_sink->destroyFrameForPaint();
return {};
}
const auto frame = _sink->frameForPaint();
const auto size = frame->yuv420.size;
const auto rotation = frame->rotation;
return (rotation == 90 || rotation == 270)
? QSize(size.height(), size.width())
: size;
}
void VideoTrack::PrepareFrameByRequests(
not_null<Frame*> frame,
int rotation) {
Expects(frame->format != FrameFormat::ARGB32
|| !frame->original.isNull());
frame->rotation = rotation;
if (frame->format != FrameFormat::ARGB32) {
return;
}
if (frame->alpha
|| !GoodForRequest(frame->original, rotation, frame->request)) {
frame->prepared = PrepareByRequest(
frame->original,
frame->alpha,
rotation,
frame->request,
std::move(frame->prepared));
}
}
} // namespace Webrtc

View File

@@ -0,0 +1,123 @@
// 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 <rpl/variable.h>
#include <QtCore/QSize>
#include <QtGui/QImage>
namespace rtc {
template <typename VideoFrameT>
class VideoSinkInterface;
} // namespace rtc
namespace webrtc {
class VideoFrame;
} // namespace webrtc
namespace Webrtc {
using SinkInterface = rtc::VideoSinkInterface<webrtc::VideoFrame>;
struct FrameRequest {
QSize resize;
QSize outer;
//ImageRoundRadius radius = ImageRoundRadius();
//RectParts corners = RectPart::AllCorners;
bool strict = true;
static FrameRequest NonStrict() {
auto result = FrameRequest();
result.strict = false;
return result;
}
[[nodiscard]] bool empty() const {
return resize.isEmpty();
}
[[nodiscard]] bool operator==(const FrameRequest &other) const {
return (resize == other.resize)
&& (outer == other.outer)/*
&& (radius == other.radius)
&& (corners == other.corners)*/;
}
[[nodiscard]] bool operator!=(const FrameRequest &other) const {
return !(*this == other);
}
[[nodiscard]] bool goodFor(const FrameRequest &other) const {
return (*this == other) || (strict && !other.strict);
}
};
enum class VideoState {
Inactive,
Paused,
Active,
};
enum class FrameFormat {
None,
ARGB32,
YUV420,
};
struct FrameChannel {
const void *data = nullptr;
int stride = 0;
};
struct FrameYUV420 {
QSize size;
QSize chromaSize;
FrameChannel y;
FrameChannel u;
FrameChannel v;
};
struct FrameWithInfo {
int64 mcstimestamp = 0;
QImage original;
FrameYUV420 *yuv420 = nullptr;
FrameFormat format = FrameFormat::None;
int rotation = 0;
int index = -1;
};
class VideoTrack final {
public:
// Called from the main thread.
explicit VideoTrack(
VideoState state,
bool requireARGB32 = true);
~VideoTrack();
void markFrameShown();
[[nodiscard]] QImage frame(const FrameRequest &request);
[[nodiscard]] FrameWithInfo frameWithInfo(bool requireARGB32) const;
[[nodiscard]] QSize frameSize() const;
[[nodiscard]] rpl::producer<> renderNextFrame() const;
[[nodiscard]] std::shared_ptr<SinkInterface> sink();
[[nodiscard]] VideoState state() const;
[[nodiscard]] rpl::producer<VideoState> stateValue() const;
[[nodiscard]] rpl::producer<VideoState> stateChanges() const;
void setState(VideoState state);
private:
class Sink;
struct Frame;
static void PrepareFrameByRequests(not_null<Frame*> frame, int rotation);
std::shared_ptr<Sink> _sink;
crl::time _inactiveFrom = 0;
rpl::variable<VideoState> _state;
};
} // namespace Webrtc