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
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:
158
Telegram/lib_webrtc/webrtc/details/webrtc_environment_openal.cpp
Normal file
158
Telegram/lib_webrtc/webrtc/details/webrtc_environment_openal.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
1231
Telegram/lib_webrtc/webrtc/details/webrtc_openal_adm.cpp
Normal file
1231
Telegram/lib_webrtc/webrtc/details/webrtc_openal_adm.cpp
Normal file
File diff suppressed because it is too large
Load Diff
209
Telegram/lib_webrtc/webrtc/details/webrtc_openal_adm.h
Normal file
209
Telegram/lib_webrtc/webrtc/details/webrtc_openal_adm.h
Normal 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
|
||||
@@ -0,0 +1,97 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webrtc/platform/linux/webrtc_environment_linux.h"
|
||||
|
||||
#include "base/debug_log.h"
|
||||
|
||||
#ifdef WEBRTC_USE_PIPEWIRE
|
||||
#include <modules/desktop_capture/linux/wayland/base_capturer_pipewire.h>
|
||||
#include <modules/portal/pipewire_utils.h>
|
||||
#endif // WEBRTC_USE_PIPEWIRE
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
EnvironmentLinux::EnvironmentLinux(not_null<EnvironmentDelegate*> delegate)
|
||||
: _audioFallback(delegate)
|
||||
, _cameraFallback(delegate) {
|
||||
#ifdef WEBRTC_USE_PIPEWIRE
|
||||
if (!webrtc::InitializePipeWire()) {
|
||||
LOG(("Audio Info: Failed to load pipewire 0.3 stubs."));
|
||||
}
|
||||
#endif // WEBRTC_USE_PIPEWIRE
|
||||
}
|
||||
|
||||
EnvironmentLinux::~EnvironmentLinux() {
|
||||
}
|
||||
|
||||
QString EnvironmentLinux::defaultId(DeviceType type) {
|
||||
return (type == DeviceType::Camera)
|
||||
? _cameraFallback.defaultId(type)
|
||||
: _audioFallback.defaultId(type);
|
||||
}
|
||||
|
||||
DeviceInfo EnvironmentLinux::device(DeviceType type, const QString &id) {
|
||||
return (type == DeviceType::Camera)
|
||||
? _cameraFallback.device(type, id)
|
||||
: _audioFallback.device(type, id);
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> EnvironmentLinux::devices(DeviceType type) {
|
||||
return (type == DeviceType::Camera)
|
||||
? _cameraFallback.devices(type)
|
||||
: _audioFallback.devices(type);
|
||||
}
|
||||
|
||||
bool EnvironmentLinux::refreshFullListOnChange(DeviceType type) {
|
||||
return (type == DeviceType::Camera)
|
||||
? _cameraFallback.refreshFullListOnChange(type)
|
||||
: _audioFallback.refreshFullListOnChange(type);
|
||||
}
|
||||
|
||||
bool EnvironmentLinux::desktopCaptureAllowed() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<QString> EnvironmentLinux::uniqueDesktopCaptureSource() const {
|
||||
#ifdef WEBRTC_USE_PIPEWIRE
|
||||
if (webrtc::DesktopCapturer::IsRunningUnderWayland()) {
|
||||
return u"desktop_capturer_pipewire"_q;
|
||||
}
|
||||
#endif // WEBRTC_USE_PIPEWIRE
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void EnvironmentLinux::defaultIdRequested(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
_cameraFallback.defaultIdRequested(type);
|
||||
} else {
|
||||
_audioFallback.defaultIdRequested(type);
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentLinux::devicesRequested(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
_cameraFallback.devicesRequested(type);
|
||||
} else {
|
||||
_audioFallback.devicesRequested(type);
|
||||
}
|
||||
}
|
||||
|
||||
DeviceResolvedId EnvironmentLinux::threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) {
|
||||
return (lastResolvedId.type == DeviceType::Camera)
|
||||
? _cameraFallback.threadSafeResolveId(lastResolvedId, savedId)
|
||||
: _audioFallback.threadSafeResolveId(lastResolvedId, savedId);
|
||||
}
|
||||
|
||||
std::unique_ptr<Environment> CreateEnvironment(
|
||||
not_null<EnvironmentDelegate*> delegate) {
|
||||
return std::make_unique<EnvironmentLinux>(delegate);
|
||||
}
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,43 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webrtc/platform/webrtc_platform_environment.h"
|
||||
|
||||
#include "webrtc/details/webrtc_environment_openal.h"
|
||||
#include "webrtc/details/webrtc_environment_video_capture.h"
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
class EnvironmentLinux final : public Environment {
|
||||
public:
|
||||
explicit EnvironmentLinux(not_null<EnvironmentDelegate*> delegate);
|
||||
~EnvironmentLinux();
|
||||
|
||||
QString defaultId(DeviceType type) override;
|
||||
DeviceInfo device(DeviceType type, const QString &id) override;
|
||||
|
||||
std::vector<DeviceInfo> devices(DeviceType type) override;
|
||||
bool refreshFullListOnChange(DeviceType type) override;
|
||||
|
||||
bool desktopCaptureAllowed() const override;
|
||||
std::optional<QString> uniqueDesktopCaptureSource() const override;
|
||||
|
||||
void defaultIdRequested(DeviceType type) override;
|
||||
void devicesRequested(DeviceType type) override;
|
||||
|
||||
DeviceResolvedId threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) override;
|
||||
|
||||
private:
|
||||
details::EnvironmentOpenAL _audioFallback;
|
||||
details::EnvironmentVideoCapture _cameraFallback;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,85 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "webrtc/platform/webrtc_platform_environment.h"
|
||||
|
||||
#include <media/engine/webrtc_media_engine.h>
|
||||
|
||||
namespace webrtc {
|
||||
template <class T>
|
||||
class scoped_refptr;
|
||||
} // namespace webrtc
|
||||
|
||||
namespace rtc {
|
||||
template <typename T>
|
||||
using scoped_refptr = webrtc::scoped_refptr<T>;
|
||||
} // namespace rtc
|
||||
|
||||
namespace webrtc {
|
||||
class TaskQueueFactory;
|
||||
class AudioDeviceModule;
|
||||
} // namespace webrtc
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
class EnvironmentMac final : public Environment, public base::has_weak_ptr {
|
||||
public:
|
||||
explicit EnvironmentMac(not_null<EnvironmentDelegate*> delegate);
|
||||
~EnvironmentMac();
|
||||
|
||||
QString defaultId(DeviceType type) override;
|
||||
DeviceInfo device(DeviceType type, const QString &id) override;
|
||||
|
||||
std::vector<DeviceInfo> devices(DeviceType type) override;
|
||||
bool refreshFullListOnChange(DeviceType type) override;
|
||||
|
||||
bool desktopCaptureAllowed() const override;
|
||||
std::optional<QString> uniqueDesktopCaptureSource() const override;
|
||||
|
||||
void defaultIdRequested(DeviceType type) override;
|
||||
void devicesRequested(DeviceType type) override;
|
||||
|
||||
void setCaptureMuted(bool muted) override;
|
||||
void setCaptureMuteTracker(
|
||||
not_null<CaptureMuteTracker*> tracker,
|
||||
bool track) override;
|
||||
|
||||
void defaultPlaybackDeviceChanged();
|
||||
void defaultCaptureDeviceChanged();
|
||||
void audioDeviceListChanged();
|
||||
|
||||
[[nodiscard]] static QString DefaultId(DeviceType type);
|
||||
|
||||
private:
|
||||
void captureMuteSubscribe();
|
||||
void captureMuteUnsubscribe();
|
||||
void captureMuteRestartAdm();
|
||||
|
||||
void sendCaptureMutedValue();
|
||||
|
||||
const not_null<EnvironmentDelegate*> _delegate;
|
||||
|
||||
base::Timer _captureMuteDebounceTimer;
|
||||
CaptureMuteTracker *_captureMuteTracker = nullptr;
|
||||
bool _captureMuteNotification = false;
|
||||
bool _captureMuted = false;
|
||||
|
||||
std::unique_ptr<webrtc::TaskQueueFactory> _admTaskQueueFactory;
|
||||
rtc::scoped_refptr<webrtc::AudioDeviceModule> _adm;
|
||||
Fn<void(DeviceResolvedId)> _admSetDeviceIdCallback;
|
||||
DeviceResolvedId _admCaptureDeviceId;
|
||||
|
||||
rpl::lifetime _captureMuteTrackerLifetime;
|
||||
rpl::lifetime _captureMuteSubscriptionLifetime;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,691 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webrtc/platform/mac/webrtc_environment_mac.h"
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
#include "webrtc/webrtc_environment.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
|
||||
#include <modules/audio_device/include/audio_device_defines.h>
|
||||
#include <modules/audio_device/include/audio_device.h>
|
||||
#include <api/task_queue/default_task_queue_factory.h>
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <IOKit/hidsystem/IOHIDLib.h>
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface InputMuteObserver : NSObject {
|
||||
}
|
||||
|
||||
- (id) init;
|
||||
- (void) inputMuteStateChange:(NSNotification *)aNotification;
|
||||
|
||||
@end // @interface InputMuteObserver
|
||||
|
||||
@implementation InputMuteObserver {
|
||||
}
|
||||
|
||||
- (id) init {
|
||||
if (self = [super init]) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) inputMuteStateChange:(NSNotification *)aNotification {
|
||||
}
|
||||
|
||||
@end // @implementation InputMuteObserver
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxNameLength = 256;
|
||||
constexpr auto kCaptureMuteDebounceTimeout = crl::time(300);
|
||||
|
||||
class EmptyCallback final : public webrtc::AudioTransport {
|
||||
public:
|
||||
int32_t RecordedDataIsAvailable(
|
||||
const void* audioSamples,
|
||||
const size_t nSamples,
|
||||
const size_t nBytesPerSample,
|
||||
const size_t nChannels,
|
||||
const uint32_t samplesPerSec,
|
||||
const uint32_t totalDelayMS,
|
||||
const int32_t clockDrift,
|
||||
const uint32_t currentMicLevel,
|
||||
const bool keyPressed,
|
||||
uint32_t& newMicLevel) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Implementation has to setup safe values for all specified out parameters.
|
||||
int32_t NeedMorePlayData(
|
||||
const size_t nSamples,
|
||||
const size_t nBytesPerSample,
|
||||
const size_t nChannels,
|
||||
const uint32_t samplesPerSec,
|
||||
void* audioSamples,
|
||||
size_t& nSamplesOut, // NOLINT
|
||||
int64_t* elapsed_time_ms,
|
||||
int64_t* ntp_time_ms) override {
|
||||
nSamplesOut = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PullRenderData(
|
||||
int bits_per_sample,
|
||||
int sample_rate,
|
||||
size_t number_of_channels,
|
||||
size_t number_of_frames,
|
||||
void* audio_data,
|
||||
int64_t* elapsed_time_ms,
|
||||
int64_t* ntp_time_ms) override {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PropertyMonitor {
|
||||
public:
|
||||
using Method = void (EnvironmentMac::*)();
|
||||
|
||||
PropertyMonitor(
|
||||
AudioObjectPropertyAddress address,
|
||||
Method method);
|
||||
|
||||
void registerEnvironment(not_null<EnvironmentMac*> environment);
|
||||
void unregisterEnvironment(not_null<EnvironmentMac*> environment);
|
||||
|
||||
private:
|
||||
void subscribe();
|
||||
void unsubscribe();
|
||||
void process();
|
||||
|
||||
static OSStatus Callback(
|
||||
AudioObjectID inObjectID,
|
||||
UInt32 inNumberAddresses,
|
||||
const AudioObjectPropertyAddress *inAddresses,
|
||||
void *inClientData);
|
||||
|
||||
const AudioObjectPropertyAddress _address = {};
|
||||
Method _method = nullptr;
|
||||
std::vector<not_null<EnvironmentMac*>> _list;
|
||||
|
||||
};
|
||||
|
||||
auto DefaultPlaybackDeviceChangedMonitor = PropertyMonitor({
|
||||
.mSelector = kAudioHardwarePropertyDefaultOutputDevice,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
}, &EnvironmentMac::defaultPlaybackDeviceChanged);
|
||||
|
||||
auto DefaultCaptureDeviceChangedMonitor = PropertyMonitor({
|
||||
.mSelector = kAudioHardwarePropertyDefaultInputDevice,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
}, &EnvironmentMac::defaultCaptureDeviceChanged);
|
||||
|
||||
auto AudioDeviceListChangedMonitor = PropertyMonitor({
|
||||
.mSelector = kAudioHardwarePropertyDevices,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
}, &EnvironmentMac::audioDeviceListChanged);
|
||||
|
||||
PropertyMonitor::PropertyMonitor(
|
||||
AudioObjectPropertyAddress address,
|
||||
Method method)
|
||||
: _address(address)
|
||||
, _method(method) {
|
||||
}
|
||||
|
||||
void PropertyMonitor::registerEnvironment(
|
||||
not_null<EnvironmentMac*> environment) {
|
||||
if (empty(_list)) {
|
||||
subscribe();
|
||||
}
|
||||
_list.push_back(environment);
|
||||
}
|
||||
|
||||
void PropertyMonitor::unregisterEnvironment(
|
||||
not_null<EnvironmentMac*> environment) {
|
||||
_list.erase(ranges::remove(_list, environment), end(_list));
|
||||
if (empty(_list)) {
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyMonitor::subscribe() {
|
||||
AudioObjectAddPropertyListener(
|
||||
kAudioObjectSystemObject,
|
||||
&_address,
|
||||
&PropertyMonitor::Callback,
|
||||
this);
|
||||
}
|
||||
|
||||
void PropertyMonitor::unsubscribe() {
|
||||
AudioObjectRemovePropertyListener(
|
||||
kAudioObjectSystemObject,
|
||||
&_address,
|
||||
&PropertyMonitor::Callback,
|
||||
this);
|
||||
}
|
||||
|
||||
void PropertyMonitor::process() {
|
||||
for (auto i = 0; i < _list.size(); ++i) {
|
||||
(_list[i]->*_method)();
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus PropertyMonitor::Callback(
|
||||
AudioObjectID inObjectID,
|
||||
UInt32 inNumberAddresses,
|
||||
const AudioObjectPropertyAddress *inAddresses,
|
||||
void *inClientData) {
|
||||
const auto monitor = static_cast<PropertyMonitor*>(inClientData);
|
||||
crl::on_main([monitor] {
|
||||
monitor->process();
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString CFStringToQString(CFStringRef text) {
|
||||
if (!text) {
|
||||
return QString();
|
||||
}
|
||||
const auto length = CFStringGetLength(text);
|
||||
const auto bytes = length * sizeof(QChar);
|
||||
auto result = QString(length, QChar(0));
|
||||
auto used = CFIndex(0);
|
||||
const auto converted = CFStringGetBytes(
|
||||
text,
|
||||
CFRange{ 0, length },
|
||||
kCFStringEncodingUTF16LE,
|
||||
UInt8(),
|
||||
false,
|
||||
reinterpret_cast<UInt8*>(result.data()),
|
||||
bytes,
|
||||
&used);
|
||||
if (!converted || !used || (used % sizeof(QChar))) {
|
||||
return QString();
|
||||
} else if (used < bytes) {
|
||||
result.resize(used / sizeof(QChar));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString GetDeviceUID(AudioDeviceID deviceId) {
|
||||
if (deviceId == kAudioDeviceUnknown) {
|
||||
return QString();
|
||||
}
|
||||
auto uid = CFStringRef(nullptr);
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (uid) {
|
||||
CFRelease(uid);
|
||||
}
|
||||
});
|
||||
auto size = UInt32(sizeof(uid));
|
||||
const auto address = AudioObjectPropertyAddress{
|
||||
.mSelector = kAudioDevicePropertyDeviceUID,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
};
|
||||
const auto result = AudioObjectGetPropertyData(
|
||||
deviceId,
|
||||
&address,
|
||||
0,
|
||||
nil,
|
||||
&size,
|
||||
&uid);
|
||||
if (result != kAudioHardwareNoError || !uid) {
|
||||
return QString();
|
||||
}
|
||||
return CFStringToQString(uid);
|
||||
}
|
||||
|
||||
[[nodiscard]] AudioDeviceID GetDeviceByUID(const QString &id) {
|
||||
const auto utf = id.toUtf8();
|
||||
auto uid = CFStringCreateWithCString(
|
||||
nullptr,
|
||||
utf.data(),
|
||||
kCFStringEncodingUTF8);
|
||||
if (!uid) {
|
||||
return kAudioObjectUnknown;
|
||||
}
|
||||
const auto guard = gsl::finally([&] {
|
||||
CFRelease(uid);
|
||||
});
|
||||
const auto address = AudioObjectPropertyAddress{
|
||||
.mSelector = kAudioHardwarePropertyDeviceForUID,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
};
|
||||
auto deviceId = AudioDeviceID(kAudioObjectUnknown);
|
||||
auto deviceSize = UInt32(sizeof(deviceId));
|
||||
|
||||
AudioValueTranslation value;
|
||||
value.mInputData = &uid;
|
||||
value.mInputDataSize = sizeof(CFStringRef);
|
||||
value.mOutputData = &deviceId;
|
||||
value.mOutputDataSize = deviceSize;
|
||||
auto valueSize = UInt32(sizeof(AudioValueTranslation));
|
||||
|
||||
const auto result = AudioObjectGetPropertyData(
|
||||
kAudioObjectSystemObject,
|
||||
&address,
|
||||
0,
|
||||
0,
|
||||
&valueSize,
|
||||
&value);
|
||||
return (result == noErr) ? deviceId : kAudioObjectUnknown;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString GetDeviceName(
|
||||
DeviceType type,
|
||||
AudioDeviceID deviceId) {
|
||||
auto name = (CFStringRef)nullptr;
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (name) {
|
||||
CFRelease(name);
|
||||
}
|
||||
});
|
||||
auto size = UInt32(sizeof(name));
|
||||
const auto address = AudioObjectPropertyAddress{
|
||||
.mSelector = kAudioDevicePropertyDeviceNameCFString,
|
||||
.mScope = (type == DeviceType::Playback
|
||||
? kAudioObjectPropertyScopeOutput
|
||||
: kAudioObjectPropertyScopeInput),
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
};
|
||||
const auto result = AudioObjectGetPropertyData(
|
||||
deviceId,
|
||||
&address,
|
||||
0,
|
||||
nullptr,
|
||||
&size,
|
||||
&name);
|
||||
if (result != kAudioHardwareNoError || !name) {
|
||||
return QString();
|
||||
}
|
||||
return CFStringToQString(name);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool DeviceHasType(
|
||||
AudioDeviceID deviceId,
|
||||
DeviceType type) {
|
||||
auto size = UInt32(0);
|
||||
const auto address = AudioObjectPropertyAddress{
|
||||
.mSelector = kAudioDevicePropertyStreamConfiguration,
|
||||
.mScope = (type == DeviceType::Playback
|
||||
? kAudioObjectPropertyScopeOutput
|
||||
: kAudioObjectPropertyScopeInput),
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
};
|
||||
auto result = AudioObjectGetPropertyDataSize(
|
||||
deviceId,
|
||||
&address,
|
||||
0,
|
||||
0,
|
||||
&size);
|
||||
if (result != noErr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
AudioBufferList *list = (AudioBufferList*)malloc(size);
|
||||
const auto guard = gsl::finally([&] {
|
||||
free(list);
|
||||
});
|
||||
result = AudioObjectGetPropertyData(
|
||||
deviceId,
|
||||
&address,
|
||||
0,
|
||||
nil,
|
||||
&size,
|
||||
list);
|
||||
return (result == noErr) && (list->mNumberBuffers > 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<AudioDeviceID> GetAllDeviceIds() {
|
||||
auto size = UInt32();
|
||||
const auto address = AudioObjectPropertyAddress{
|
||||
.mSelector = kAudioHardwarePropertyDevices,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
};
|
||||
|
||||
auto result = AudioObjectGetPropertyDataSize(
|
||||
kAudioObjectSystemObject,
|
||||
&address,
|
||||
0,
|
||||
nil,
|
||||
&size);
|
||||
if (result != noErr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto count = size / sizeof(AudioDeviceID);
|
||||
auto list = std::vector<AudioDeviceID>(count, kAudioDeviceUnknown);
|
||||
result = AudioObjectGetPropertyData(
|
||||
kAudioObjectSystemObject,
|
||||
&address,
|
||||
0,
|
||||
nil,
|
||||
&size,
|
||||
list.data());
|
||||
return (result == noErr) ? list : std::vector<AudioDeviceID>();
|
||||
}
|
||||
|
||||
[[nodiscard]] QString NS2QString(NSString *text) {
|
||||
return QString::fromUtf8(
|
||||
[text cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString CaptureDeviceId(AVCaptureDevice *device) {
|
||||
return device ? NS2QString([device uniqueID]) : QString();
|
||||
}
|
||||
|
||||
[[nodiscard]] DeviceInfo CaptureDeviceInfo(AVCaptureDevice *device) {
|
||||
const auto id = CaptureDeviceId(device);
|
||||
if (id.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
.id = id,
|
||||
.name = NS2QString([device localizedName]),
|
||||
.type = DeviceType::Camera,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EnvironmentMac::EnvironmentMac(not_null<EnvironmentDelegate*> delegate)
|
||||
: _delegate(delegate)
|
||||
, _captureMuteDebounceTimer([=] { sendCaptureMutedValue(); }) {
|
||||
DefaultPlaybackDeviceChangedMonitor.registerEnvironment(this);
|
||||
DefaultCaptureDeviceChangedMonitor.registerEnvironment(this);
|
||||
AudioDeviceListChangedMonitor.registerEnvironment(this);
|
||||
|
||||
if (@available(macOS 14.0, *)) {
|
||||
const auto weak = base::make_weak(this);
|
||||
id block = [^(BOOL shouldBeMuted){
|
||||
crl::on_main([weak, mute = shouldBeMuted ? true : false] {
|
||||
if (const auto strong = weak.get()) {
|
||||
if (strong->_captureMuteTracker) {
|
||||
strong->_captureMuted = mute;
|
||||
strong->_captureMuteDebounceTimer.callOnce(
|
||||
kCaptureMuteDebounceTimeout);
|
||||
}
|
||||
}
|
||||
});
|
||||
return YES;
|
||||
} copy];
|
||||
_lifetime.add([block] { [block release]; });
|
||||
|
||||
[[AVAudioApplication sharedInstance]
|
||||
setInputMuteStateChangeHandler:block
|
||||
error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
EnvironmentMac::~EnvironmentMac() {
|
||||
DefaultPlaybackDeviceChangedMonitor.unregisterEnvironment(this);
|
||||
DefaultCaptureDeviceChangedMonitor.unregisterEnvironment(this);
|
||||
AudioDeviceListChangedMonitor.unregisterEnvironment(this);
|
||||
}
|
||||
|
||||
void EnvironmentMac::sendCaptureMutedValue() {
|
||||
Expects(_captureMuteTracker != nullptr);
|
||||
|
||||
const auto weak = base::make_weak(this);
|
||||
_captureMuteNotification = true;
|
||||
_captureMuteTracker->captureMuteChanged(_captureMuted);
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->_captureMuteNotification = false;
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentMac::defaultPlaybackDeviceChanged() {
|
||||
const auto type = DeviceType::Playback;
|
||||
_delegate->defaultChanged(type, DeviceChangeReason::Manual, defaultId(type));
|
||||
}
|
||||
|
||||
void EnvironmentMac::defaultCaptureDeviceChanged() {
|
||||
const auto type = DeviceType::Capture;
|
||||
_delegate->defaultChanged(type, DeviceChangeReason::Manual, defaultId(type));
|
||||
}
|
||||
|
||||
void EnvironmentMac::audioDeviceListChanged() {
|
||||
_delegate->devicesForceRefresh(DeviceType::Playback);
|
||||
_delegate->devicesForceRefresh(DeviceType::Capture);
|
||||
}
|
||||
|
||||
QString EnvironmentMac::defaultId(DeviceType type) {
|
||||
return DefaultId(type);
|
||||
}
|
||||
|
||||
QString EnvironmentMac::DefaultId(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return CaptureDeviceId(
|
||||
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]);
|
||||
}
|
||||
|
||||
auto deviceId = AudioDeviceID(kAudioDeviceUnknown);
|
||||
auto size = UInt32(sizeof(deviceId));
|
||||
const auto address = AudioObjectPropertyAddress{
|
||||
.mSelector = (type == DeviceType::Playback
|
||||
? kAudioHardwarePropertyDefaultOutputDevice
|
||||
: kAudioHardwarePropertyDefaultInputDevice),
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMain,
|
||||
};
|
||||
const auto result = AudioObjectGetPropertyData(
|
||||
kAudioObjectSystemObject,
|
||||
&address,
|
||||
0,
|
||||
0,
|
||||
&size,
|
||||
&deviceId);
|
||||
return (result == kAudioHardwareNoError) ? GetDeviceUID(deviceId) : QString();
|
||||
}
|
||||
|
||||
DeviceInfo EnvironmentMac::device(DeviceType type, const QString &id) {
|
||||
if (type == DeviceType::Camera) {
|
||||
NSArray<AVCaptureDevice*> *devices
|
||||
= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
for (AVCaptureDevice *device in devices) {
|
||||
if (CaptureDeviceId(device) == id) {
|
||||
return CaptureDeviceInfo(device);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
const auto deviceId = GetDeviceByUID(id);
|
||||
if (deviceId != kAudioDeviceUnknown) {
|
||||
return {
|
||||
.id = id,
|
||||
.name = GetDeviceName(type, deviceId),
|
||||
.type = type,
|
||||
.inactive = false,
|
||||
};
|
||||
}
|
||||
const auto list = devices(type);
|
||||
return list.empty() ? DeviceInfo() : list.front();
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> EnvironmentMac::devices(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
auto result = std::vector<DeviceInfo>();
|
||||
const auto add = [&](AVCaptureDevice *device) {
|
||||
if (auto info = CaptureDeviceInfo(device)) {
|
||||
if (!ranges::contains(result, info.id, &DeviceInfo::id)) {
|
||||
result.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
};
|
||||
add([AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]);
|
||||
NSArray<AVCaptureDevice*> *devices
|
||||
= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
for (AVCaptureDevice *device in devices) {
|
||||
add(device);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
auto result = std::vector<DeviceInfo>();
|
||||
for (const auto deviceId : GetAllDeviceIds()) {
|
||||
if (DeviceHasType(deviceId, type)) {
|
||||
if (const auto uid = GetDeviceUID(deviceId); !uid.isEmpty()) {
|
||||
result.push_back({
|
||||
.id = uid,
|
||||
.name = GetDeviceName(type, deviceId),
|
||||
.type = type,
|
||||
.inactive = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EnvironmentMac::refreshFullListOnChange(DeviceType type) {
|
||||
// We have simple change notifications, no exact change information.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EnvironmentMac::desktopCaptureAllowed() const {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
// Screen Recording is required on macOS 10.15 an later.
|
||||
// Even if user grants access, restart is required.
|
||||
static const auto result = CGPreflightScreenCaptureAccess();
|
||||
return result;
|
||||
} else if (@available(macOS 10.15, *)) {
|
||||
const auto stream = CGDisplayStreamCreate(
|
||||
CGMainDisplayID(),
|
||||
1,
|
||||
1,
|
||||
kCVPixelFormatType_32BGRA,
|
||||
CFDictionaryRef(),
|
||||
^(
|
||||
CGDisplayStreamFrameStatus status,
|
||||
uint64_t display_time,
|
||||
IOSurfaceRef frame_surface,
|
||||
CGDisplayStreamUpdateRef updateRef) {
|
||||
});
|
||||
if (!stream) {
|
||||
return false;
|
||||
}
|
||||
CFRelease(stream);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<QString> EnvironmentMac::uniqueDesktopCaptureSource() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
void EnvironmentMac::defaultIdRequested(DeviceType type) {
|
||||
}
|
||||
|
||||
void EnvironmentMac::devicesRequested(DeviceType type) {
|
||||
}
|
||||
|
||||
void EnvironmentMac::setCaptureMuted(bool muted) {
|
||||
if (@available(macOS 14.0, *)) {
|
||||
if (!_captureMuteNotification) {
|
||||
const auto value = muted ? YES : NO;
|
||||
[[AVAudioApplication sharedInstance] setInputMuted:value error:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentMac::captureMuteSubscribe() {
|
||||
if (@available(macOS 14.0, *)) {
|
||||
id observer = [[InputMuteObserver alloc] init];
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter]
|
||||
addObserver:observer
|
||||
selector:@selector(inputMuteStateChange:)
|
||||
name:AVAudioApplicationInputMuteStateChangeNotification
|
||||
object:nil];
|
||||
|
||||
_admTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory();
|
||||
const auto saveSetDeviceIdCallback = [=](
|
||||
Fn<void(DeviceResolvedId)> setDeviceIdCallback) {
|
||||
_admSetDeviceIdCallback = std::move(setDeviceIdCallback);
|
||||
if (!_admCaptureDeviceId.isDefault()) {
|
||||
_admSetDeviceIdCallback(_admCaptureDeviceId);
|
||||
}
|
||||
};
|
||||
_adm = CreateAudioDeviceModule(
|
||||
_admTaskQueueFactory.get(),
|
||||
saveSetDeviceIdCallback);
|
||||
|
||||
// We don't need captured data, we need simply to have active recording.
|
||||
static auto kEmptyCallback = EmptyCallback();
|
||||
_adm->RegisterAudioCallback(&kEmptyCallback);
|
||||
|
||||
_captureMuteSubscriptionLifetime.add([=] {
|
||||
_admSetDeviceIdCallback = nullptr;
|
||||
_adm = nullptr;
|
||||
_admTaskQueueFactory = nullptr;
|
||||
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter]
|
||||
removeObserver:observer
|
||||
name:AVAudioApplicationInputMuteStateChangeNotification
|
||||
object:nil];
|
||||
[observer release];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentMac::captureMuteUnsubscribe() {
|
||||
_captureMuteSubscriptionLifetime.destroy();
|
||||
}
|
||||
|
||||
void EnvironmentMac::captureMuteRestartAdm() {
|
||||
_adm->StopRecording();
|
||||
_adm->SetRecordingDevice(0);
|
||||
if (_adm->InitRecording() == 0) {
|
||||
_adm->StartRecording();
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentMac::setCaptureMuteTracker(
|
||||
not_null<CaptureMuteTracker*> tracker,
|
||||
bool track) {
|
||||
if (@available(macOS 14.0, *)) {
|
||||
if (track) {
|
||||
if (!_captureMuteTracker) {
|
||||
captureMuteSubscribe();
|
||||
} else if (_captureMuteTracker == tracker) {
|
||||
return;
|
||||
}
|
||||
_captureMuteTrackerLifetime.destroy();
|
||||
_captureMuteTracker = tracker;
|
||||
_captureMuteTracker->captureMuteDeviceId(
|
||||
) | rpl::on_next([=](DeviceResolvedId deviceId) {
|
||||
_admSetDeviceIdCallback(deviceId);
|
||||
captureMuteRestartAdm();
|
||||
}, _captureMuteTrackerLifetime);
|
||||
} else if (_captureMuteTracker == tracker) {
|
||||
_captureMuteTrackerLifetime.destroy();
|
||||
_captureMuteDebounceTimer.cancel();
|
||||
_captureMuteTracker = nullptr;
|
||||
captureMuteUnsubscribe();
|
||||
if (!_captureMuted) {
|
||||
_captureMuted = true;
|
||||
setCaptureMuted(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Environment> CreateEnvironment(
|
||||
not_null<EnvironmentDelegate*> delegate) {
|
||||
return std::make_unique<EnvironmentMac>(delegate);
|
||||
}
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,56 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webrtc/webrtc_device_common.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
class EnvironmentDelegate;
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
virtual ~Environment() = default;
|
||||
|
||||
[[nodiscard]] virtual QString defaultId(DeviceType type) = 0;
|
||||
[[nodiscard]] virtual DeviceInfo device(
|
||||
DeviceType type,
|
||||
const QString &id) = 0;
|
||||
|
||||
[[nodiscard]] virtual std::vector<DeviceInfo> devices(
|
||||
DeviceType type) = 0;
|
||||
[[nodiscard]] virtual bool refreshFullListOnChange(
|
||||
DeviceType type) = 0;
|
||||
|
||||
[[nodiscard]] virtual bool desktopCaptureAllowed() const = 0;
|
||||
[[nodiscard]] virtual auto uniqueDesktopCaptureSource() const
|
||||
-> std::optional<QString> = 0;
|
||||
|
||||
virtual void defaultIdRequested(DeviceType type) = 0;
|
||||
virtual void devicesRequested(DeviceType type) = 0;
|
||||
|
||||
[[nodiscard]] virtual DeviceResolvedId threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) {
|
||||
return lastResolvedId;
|
||||
}
|
||||
|
||||
virtual void setCaptureMuted(bool muted) {
|
||||
}
|
||||
virtual void setCaptureMuteTracker(
|
||||
not_null<CaptureMuteTracker*> tracker,
|
||||
bool track) {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Environment> CreateEnvironment(
|
||||
not_null<EnvironmentDelegate*> delegate);
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,408 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webrtc/platform/win/webrtc_environment_win.h"
|
||||
|
||||
#include "base/platform/win/base_windows_co_task_mem.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "webrtc/webrtc_environment.h"
|
||||
|
||||
#include <MMDeviceAPI.h>
|
||||
#include <winrt/base.h>
|
||||
|
||||
#include <functiondiscoverykeys_devpkey.h>
|
||||
#include <propvarutil.h>
|
||||
#include <propkey.h>
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxNameLength = 256;
|
||||
|
||||
[[nodiscard]] auto RoleForType(DeviceType type) {
|
||||
return (type == DeviceType::Playback) ? eConsole : eCommunications;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class EnvironmentWin::Client
|
||||
: public winrt::implements<Client, IMMNotificationClient>
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
Client(
|
||||
Fn<void(DeviceType, QString)> defaultChanged,
|
||||
Fn<void(QString, std::optional<DeviceStateChange>)> deviceToggled);
|
||||
|
||||
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
|
||||
LPCWSTR deviceId,
|
||||
const PROPERTYKEY key) override;
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR deviceId) override;
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR deviceId) override;
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
|
||||
LPCWSTR deviceId,
|
||||
DWORD newState) override;
|
||||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(
|
||||
EDataFlow flow,
|
||||
ERole role,
|
||||
LPCWSTR newDefaultDeviceId) override;
|
||||
|
||||
private:
|
||||
Fn<void(DeviceType, QString)> _defaultChanged;
|
||||
Fn<void(QString, std::optional<DeviceStateChange>)> _deviceToggled;
|
||||
|
||||
};
|
||||
|
||||
EnvironmentWin::Client::Client(
|
||||
Fn<void(DeviceType, QString)> defaultChanged,
|
||||
Fn<void(QString, std::optional<DeviceStateChange>)> deviceToggled)
|
||||
: _defaultChanged(std::move(defaultChanged))
|
||||
, _deviceToggled(std::move(deviceToggled)) {
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnPropertyValueChanged(
|
||||
LPCWSTR deviceId,
|
||||
const PROPERTYKEY key) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnDeviceAdded(
|
||||
LPCWSTR deviceId) {
|
||||
const auto id = QString::fromWCharArray(deviceId);
|
||||
crl::on_main(this, [=] {
|
||||
const auto onstack = _deviceToggled;
|
||||
onstack(id, std::nullopt);
|
||||
});
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnDeviceRemoved(
|
||||
LPCWSTR deviceId) {
|
||||
const auto id = QString::fromWCharArray(deviceId);
|
||||
const auto change = DeviceStateChange::Disconnected;
|
||||
crl::on_main(this, [=] {
|
||||
const auto onstack = _deviceToggled;
|
||||
onstack(id, change);
|
||||
});
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnDeviceStateChanged(
|
||||
LPCWSTR deviceId,
|
||||
DWORD newState) {
|
||||
const auto change = (newState == DEVICE_STATE_ACTIVE)
|
||||
? DeviceStateChange::Active
|
||||
: DeviceStateChange::Inactive;
|
||||
const auto id = QString::fromWCharArray(deviceId);
|
||||
crl::on_main(this, [=] {
|
||||
const auto onstack = _deviceToggled;
|
||||
onstack(id, change);
|
||||
});
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EnvironmentWin::Client::OnDefaultDeviceChanged(
|
||||
EDataFlow flow,
|
||||
ERole role,
|
||||
LPCWSTR newDefaultDeviceId) {
|
||||
const auto type = (flow == eRender)
|
||||
? DeviceType::Playback
|
||||
: DeviceType::Capture;
|
||||
if (role == RoleForType(type)) {
|
||||
const auto id = QString::fromWCharArray(newDefaultDeviceId);
|
||||
crl::on_main(this, [=] {
|
||||
const auto onstack = _defaultChanged;
|
||||
onstack(type, id);
|
||||
});
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
EnvironmentWin::EnvironmentWin(not_null<EnvironmentDelegate*> delegate)
|
||||
: _delegate(delegate)
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
, _audioFallback(delegate)
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
, _cameraFallback(delegate) {
|
||||
#ifndef WEBRTC_TESTING_OPENAL
|
||||
using namespace base::WinRT;
|
||||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||||
_enumerator = TryCreateInstance<IMMDeviceEnumerator>(
|
||||
CLSID_MMDeviceEnumerator);
|
||||
if (!_enumerator) {
|
||||
const auto hr = CoInitialize(nullptr);
|
||||
if (SUCCEEDED(hr)) {
|
||||
_comInitialized = true;
|
||||
_enumerator = TryCreateInstance<IMMDeviceEnumerator>(
|
||||
CLSID_MMDeviceEnumerator);
|
||||
if (!_enumerator) {
|
||||
LOG(("Media Error: Could not create MMDeviceEnumerator."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_client = winrt::make<Client>([=](DeviceType type, QString id) {
|
||||
_delegate->defaultChanged(type, DeviceChangeReason::Manual, id);
|
||||
}, [=](QString id, std::optional<DeviceStateChange> state) {
|
||||
processDeviceStateChange(id, state);
|
||||
});
|
||||
if (!_client) {
|
||||
LOG(("Media Error: Could not create IMMNotificationClient."));
|
||||
return;
|
||||
}
|
||||
const auto hr = _enumerator->RegisterEndpointNotificationCallback(
|
||||
_client.get());
|
||||
if (FAILED(hr)) {
|
||||
LOG(("Media Error: RegisterEndpointNotificationCallback failed."));
|
||||
}
|
||||
#endif // !WEBRTC_TESTING_OPENAL
|
||||
}
|
||||
|
||||
EnvironmentWin::~EnvironmentWin() {
|
||||
if (_client) {
|
||||
Assert(_enumerator != nullptr);
|
||||
_enumerator->UnregisterEndpointNotificationCallback(_client.get());
|
||||
_client = nullptr;
|
||||
}
|
||||
_enumerator = nullptr;
|
||||
if (_comInitialized) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
QString EnvironmentWin::defaultId(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return _cameraFallback.defaultId(type);
|
||||
} else if (!_enumerator) {
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
return _audioFallback.defaultId(type);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
return {};
|
||||
}
|
||||
const auto flow = (type == DeviceType::Playback)
|
||||
? eRender
|
||||
: eCapture;
|
||||
const auto role = RoleForType(type);
|
||||
|
||||
auto device = winrt::com_ptr<IMMDevice>();
|
||||
auto hr = _enumerator->GetDefaultAudioEndpoint(flow, role, device.put());
|
||||
if (FAILED(hr) || !device) {
|
||||
return {};
|
||||
}
|
||||
auto id = base::CoTaskMemString();
|
||||
hr = device->GetId(id.put());
|
||||
if (FAILED(hr) || !id || !*id.data()) {
|
||||
return {};
|
||||
}
|
||||
return QString::fromWCharArray(id.data());
|
||||
}
|
||||
|
||||
void EnvironmentWin::processDeviceStateChange(
|
||||
const QString &id,
|
||||
std::optional<DeviceStateChange> change) {
|
||||
const auto wide = id.toStdWString();
|
||||
auto device = winrt::com_ptr<IMMDevice>();
|
||||
auto hr = _enumerator->GetDevice(wide.c_str(), device.put());
|
||||
if (FAILED(hr)) {
|
||||
return;
|
||||
} else if (const auto endpoint = device.try_as<IMMEndpoint>()) {
|
||||
auto flow = EDataFlow();
|
||||
hr = endpoint->GetDataFlow(&flow);
|
||||
if (SUCCEEDED(hr)) {
|
||||
if (!change) {
|
||||
auto state = DWORD();
|
||||
hr = device->GetState(&state);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return;
|
||||
}
|
||||
change = (state == DEVICE_STATE_ACTIVE)
|
||||
? DeviceStateChange::Active
|
||||
: DeviceStateChange::Inactive;
|
||||
}
|
||||
const auto type = (flow == eRender)
|
||||
? DeviceType::Playback
|
||||
: DeviceType::Capture;
|
||||
_delegate->deviceStateChanged(type, id, *change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeviceInfo EnvironmentWin::device(DeviceType type, const QString &id) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return _cameraFallback.device(type, id);
|
||||
} else if (!_enumerator) {
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
return _audioFallback.device(type, id);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
return {};
|
||||
}
|
||||
const auto wide = id.toStdWString();
|
||||
auto device = winrt::com_ptr<IMMDevice>();
|
||||
auto hr = _enumerator->GetDevice(wide.c_str(), device.put());
|
||||
if (FAILED(hr) || !device) {
|
||||
return {};
|
||||
}
|
||||
auto store = winrt::com_ptr<IPropertyStore>();
|
||||
hr = device->OpenPropertyStore(STGM_READ, store.put());
|
||||
if (FAILED(hr) || !store) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto name = PROPVARIANT();
|
||||
hr = store->GetValue(PKEY_Device_FriendlyName, &name);
|
||||
if (FAILED(hr)) {
|
||||
return {};
|
||||
}
|
||||
const auto guard = gsl::finally([&] { PropVariantClear(&name); });
|
||||
|
||||
auto already = std::array<WCHAR, kMaxNameLength>();
|
||||
hr = PropVariantToString(name, already.data(), MAX_PATH);
|
||||
if (FAILED(hr) || !already[0]) {
|
||||
return {};
|
||||
}
|
||||
auto state = DWORD();
|
||||
hr = device->GetState(&state);
|
||||
if (FAILED(hr)) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
.id = id,
|
||||
.name = QString::fromWCharArray(already.data()),
|
||||
.type = type,
|
||||
.inactive = (state != DEVICE_STATE_ACTIVE),
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> EnvironmentWin::devices(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return _cameraFallback.devices(type);
|
||||
} else if (!_enumerator) {
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
return _audioFallback.devices(type);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
return {};
|
||||
}
|
||||
const auto flow = (type == DeviceType::Playback)
|
||||
? eRender
|
||||
: eCapture;
|
||||
auto collection = winrt::com_ptr<IMMDeviceCollection>();
|
||||
auto hr = _enumerator->EnumAudioEndpoints(
|
||||
flow,
|
||||
DEVICE_STATEMASK_ALL,
|
||||
collection.put());
|
||||
if (FAILED(hr) || !collection) {
|
||||
return {};
|
||||
}
|
||||
auto count = UINT();
|
||||
hr = collection->GetCount(&count);
|
||||
if (FAILED(hr)) {
|
||||
return {};
|
||||
}
|
||||
auto result = std::vector<DeviceInfo>();
|
||||
result.reserve(count);
|
||||
for (auto i = UINT(); i != count; ++i) {
|
||||
auto device = winrt::com_ptr<IMMDevice>();
|
||||
hr = collection->Item(i, device.put());
|
||||
if (FAILED(hr)) {
|
||||
continue;
|
||||
}
|
||||
auto endpoint = device.try_as<IMMEndpoint>();
|
||||
if (!endpoint) {
|
||||
continue;
|
||||
}
|
||||
auto flow = EDataFlow();
|
||||
hr = endpoint->GetDataFlow(&flow);
|
||||
auto id = base::CoTaskMemString();
|
||||
hr = device->GetId(id.put());
|
||||
if (FAILED(hr) || !id || !*id.data()) {
|
||||
continue;
|
||||
}
|
||||
auto store = winrt::com_ptr<IPropertyStore>();
|
||||
hr = device->OpenPropertyStore(STGM_READ, store.put());
|
||||
if (FAILED(hr) || !store) {
|
||||
continue;
|
||||
}
|
||||
auto name = PROPVARIANT();
|
||||
hr = store->GetValue(PKEY_Device_FriendlyName, &name);
|
||||
if (FAILED(hr)) {
|
||||
continue;
|
||||
}
|
||||
const auto guard = gsl::finally([&] { PropVariantClear(&name); });
|
||||
|
||||
auto already = std::array<WCHAR, kMaxNameLength>();
|
||||
hr = PropVariantToString(name, already.data(), MAX_PATH);
|
||||
if (FAILED(hr) || !already[0]) {
|
||||
continue;
|
||||
}
|
||||
auto state = DWORD();
|
||||
hr = device->GetState(&state);
|
||||
if (FAILED(hr)) {
|
||||
continue;
|
||||
}
|
||||
result.push_back({
|
||||
.id = QString::fromWCharArray(id.data()),
|
||||
.name = QString::fromWCharArray(already.data()),
|
||||
.type = type,
|
||||
.inactive = (state != DEVICE_STATE_ACTIVE),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EnvironmentWin::refreshFullListOnChange(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
return _cameraFallback.refreshFullListOnChange(type);
|
||||
}
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
return true;
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EnvironmentWin::desktopCaptureAllowed() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<QString> EnvironmentWin::uniqueDesktopCaptureSource() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
void EnvironmentWin::defaultIdRequested(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
_cameraFallback.defaultIdRequested(type);
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
} else {
|
||||
_audioFallback.defaultIdRequested(type);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentWin::devicesRequested(DeviceType type) {
|
||||
if (type == DeviceType::Camera) {
|
||||
_cameraFallback.devicesRequested(type);
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
} else {
|
||||
_audioFallback.devicesRequested(type);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
}
|
||||
}
|
||||
|
||||
DeviceResolvedId EnvironmentWin::threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) {
|
||||
return (lastResolvedId.type == DeviceType::Camera)
|
||||
? _cameraFallback.threadSafeResolveId(lastResolvedId, savedId)
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
: _audioFallback.threadSafeResolveId(lastResolvedId, savedId);
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
: lastResolvedId;
|
||||
}
|
||||
|
||||
std::unique_ptr<Environment> CreateEnvironment(
|
||||
not_null<EnvironmentDelegate*> delegate) {
|
||||
return std::make_unique<EnvironmentWin>(delegate);
|
||||
}
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
@@ -0,0 +1,65 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webrtc/platform/webrtc_platform_environment.h"
|
||||
|
||||
#include "webrtc/details/webrtc_environment_video_capture.h"
|
||||
#include "base/platform/win/base_windows_winrt.h"
|
||||
|
||||
//#define WEBRTC_TESTING_OPENAL
|
||||
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
#include "webrtc/details/webrtc_environment_openal.h"
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
|
||||
struct IMMDeviceEnumerator;
|
||||
struct IMMNotificationClient;
|
||||
|
||||
namespace Webrtc::Platform {
|
||||
|
||||
class EnvironmentWin final : public Environment {
|
||||
public:
|
||||
explicit EnvironmentWin(not_null<EnvironmentDelegate*> delegate);
|
||||
~EnvironmentWin();
|
||||
|
||||
QString defaultId(DeviceType type) override;
|
||||
DeviceInfo device(DeviceType type, const QString &id) override;
|
||||
|
||||
std::vector<DeviceInfo> devices(DeviceType type) override;
|
||||
bool refreshFullListOnChange(DeviceType type) override;
|
||||
|
||||
bool desktopCaptureAllowed() const override;
|
||||
std::optional<QString> uniqueDesktopCaptureSource() const override;
|
||||
|
||||
void defaultIdRequested(DeviceType type) override;
|
||||
void devicesRequested(DeviceType type) override;
|
||||
|
||||
DeviceResolvedId threadSafeResolveId(
|
||||
const DeviceResolvedId &lastResolvedId,
|
||||
const QString &savedId) override;
|
||||
|
||||
private:
|
||||
void processDeviceStateChange(
|
||||
const QString &id,
|
||||
std::optional<DeviceStateChange> change);
|
||||
|
||||
class Client;
|
||||
|
||||
const not_null<EnvironmentDelegate*> _delegate;
|
||||
#ifdef WEBRTC_TESTING_OPENAL
|
||||
details::EnvironmentOpenAL _audioFallback;
|
||||
#endif // WEBRTC_TESTING_OPENAL
|
||||
details::EnvironmentVideoCapture _cameraFallback;
|
||||
|
||||
bool _comInitialized = false;
|
||||
winrt::com_ptr<IMMDeviceEnumerator> _enumerator;
|
||||
winrt::com_ptr<IMMNotificationClient> _client;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webrtc::Platform
|
||||
1149
Telegram/lib_webrtc/webrtc/platform/win/webrtc_loopback_adm_win.cpp
Normal file
1149
Telegram/lib_webrtc/webrtc/platform/win/webrtc_loopback_adm_win.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,198 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <modules/audio_device/include/audio_device.h>
|
||||
#include <modules/audio_device/audio_device_buffer.h>
|
||||
#include <api/scoped_refptr.h>
|
||||
|
||||
#include <AudioClient.h>
|
||||
#include <MMDeviceAPI.h>
|
||||
|
||||
#include <winrt/base.h>
|
||||
|
||||
typedef struct SwrContext SwrContext;
|
||||
typedef struct AVChannelLayout AVChannelLayout;
|
||||
|
||||
namespace rtc {
|
||||
class Thread;
|
||||
} // namespace rtc
|
||||
|
||||
namespace webrtc {
|
||||
class AudioProcessing;
|
||||
class AudioFrame;
|
||||
} // namespace webrtc
|
||||
|
||||
namespace Webrtc::details {
|
||||
|
||||
[[nodiscard]] bool IsLoopbackCaptureActive();
|
||||
|
||||
void LoopbackCapturePushFarEnd(
|
||||
crl::time when,
|
||||
const QByteArray &samples,
|
||||
int frequency,
|
||||
int channels);
|
||||
|
||||
class AudioDeviceLoopbackWin : public webrtc::AudioDeviceModule {
|
||||
public:
|
||||
explicit AudioDeviceLoopbackWin(webrtc::TaskQueueFactory *taskQueueFactory);
|
||||
~AudioDeviceLoopbackWin();
|
||||
|
||||
int32_t ActiveAudioLayer(AudioLayer *audioLayer) const override;
|
||||
int32_t RegisterAudioCallback(
|
||||
webrtc::AudioTransport *audioCallback) override;
|
||||
|
||||
// Main initializaton and termination
|
||||
int32_t Init() override;
|
||||
int32_t Terminate() override;
|
||||
bool Initialized() const override;
|
||||
|
||||
// Device enumeration
|
||||
int16_t PlayoutDevices() override;
|
||||
int16_t RecordingDevices() override;
|
||||
int32_t PlayoutDeviceName(uint16_t index,
|
||||
char name[webrtc::kAdmMaxDeviceNameSize],
|
||||
char guid[webrtc::kAdmMaxGuidSize]) override;
|
||||
int32_t RecordingDeviceName(uint16_t index,
|
||||
char name[webrtc::kAdmMaxDeviceNameSize],
|
||||
char guid[webrtc::kAdmMaxGuidSize]) override;
|
||||
|
||||
// Device selection
|
||||
int32_t SetPlayoutDevice(uint16_t index) override;
|
||||
int32_t SetPlayoutDevice(WindowsDeviceType device) override;
|
||||
int32_t SetRecordingDevice(uint16_t index) override;
|
||||
int32_t SetRecordingDevice(WindowsDeviceType device) override;
|
||||
|
||||
// Audio transport initialization
|
||||
int32_t PlayoutIsAvailable(bool *available) override;
|
||||
int32_t InitPlayout() override;
|
||||
bool PlayoutIsInitialized() const override;
|
||||
int32_t RecordingIsAvailable(bool *available) override;
|
||||
int32_t InitRecording() override;
|
||||
bool RecordingIsInitialized() const override;
|
||||
|
||||
// Audio transport control
|
||||
int32_t StartPlayout() override;
|
||||
int32_t StopPlayout() override;
|
||||
bool Playing() const override;
|
||||
int32_t StartRecording() override;
|
||||
int32_t StopRecording() override;
|
||||
bool Recording() const override;
|
||||
|
||||
// Audio mixer initialization
|
||||
int32_t InitSpeaker() override;
|
||||
bool SpeakerIsInitialized() const override;
|
||||
int32_t InitMicrophone() override;
|
||||
bool MicrophoneIsInitialized() const override;
|
||||
|
||||
// Speaker volume controls
|
||||
int32_t SpeakerVolumeIsAvailable(bool *available) override;
|
||||
int32_t SetSpeakerVolume(uint32_t volume) override;
|
||||
int32_t SpeakerVolume(uint32_t *volume) const override;
|
||||
int32_t MaxSpeakerVolume(uint32_t *maxVolume) const override;
|
||||
int32_t MinSpeakerVolume(uint32_t *minVolume) const override;
|
||||
|
||||
// Microphone volume controls
|
||||
int32_t MicrophoneVolumeIsAvailable(bool *available) override;
|
||||
int32_t SetMicrophoneVolume(uint32_t volume) override;
|
||||
int32_t MicrophoneVolume(uint32_t *volume) const override;
|
||||
int32_t MaxMicrophoneVolume(uint32_t *maxVolume) const override;
|
||||
int32_t MinMicrophoneVolume(uint32_t *minVolume) const override;
|
||||
|
||||
// Microphone mute control
|
||||
int32_t MicrophoneMuteIsAvailable(bool *available) override;
|
||||
int32_t SetMicrophoneMute(bool enable) override;
|
||||
int32_t MicrophoneMute(bool *enabled) const override;
|
||||
|
||||
// Speaker mute control
|
||||
int32_t SpeakerMuteIsAvailable(bool *available) override;
|
||||
int32_t SetSpeakerMute(bool enable) override;
|
||||
int32_t SpeakerMute(bool *enabled) const override;
|
||||
|
||||
// Stereo support
|
||||
int32_t StereoPlayoutIsAvailable(bool *available) const override;
|
||||
int32_t SetStereoPlayout(bool enable) override;
|
||||
int32_t StereoPlayout(bool *enabled) const override;
|
||||
int32_t StereoRecordingIsAvailable(bool *available) const override;
|
||||
int32_t SetStereoRecording(bool enable) override;
|
||||
int32_t StereoRecording(bool *enabled) const override;
|
||||
|
||||
// Delay information and control
|
||||
int32_t PlayoutDelay(uint16_t *delayMS) const override;
|
||||
|
||||
// Only supported on Android.
|
||||
bool BuiltInAECIsAvailable() const override;
|
||||
bool BuiltInAGCIsAvailable() const override;
|
||||
bool BuiltInNSIsAvailable() const override;
|
||||
|
||||
// Enables the built-in audio effects. Only supported on Android.
|
||||
int32_t EnableBuiltInAEC(bool enable) override;
|
||||
int32_t EnableBuiltInAGC(bool enable) override;
|
||||
int32_t EnableBuiltInNS(bool enable) override;
|
||||
|
||||
private:
|
||||
void openPlaybackDeviceForCapture();
|
||||
void openAudioClient();
|
||||
|
||||
void openRecordingDevice();
|
||||
void closeRecordingDevice();
|
||||
|
||||
void captureFailed(const std::string &error);
|
||||
|
||||
void startCaptureOnThread();
|
||||
void stopCaptureOnThread();
|
||||
void processData();
|
||||
|
||||
bool setupResampler(const WAVEFORMATEX &format);
|
||||
bool setupResampler(
|
||||
int channels,
|
||||
AVChannelLayout channelLayout,
|
||||
int inputFormat, // AVSampleFormat
|
||||
int sampleRate,
|
||||
Fn<std::string()> info);
|
||||
void ensureResampleSpaceAvailable(int samples);
|
||||
|
||||
static DWORD WINAPI CaptureThreadMethod(LPVOID context);
|
||||
DWORD runCaptureThread();
|
||||
|
||||
webrtc::AudioDeviceBuffer _audioDeviceBuffer;
|
||||
|
||||
winrt::com_ptr<IMMDevice> _endpointDevice;
|
||||
winrt::com_ptr<IAudioClient> _audioClient;
|
||||
winrt::com_ptr<IAudioClient> _audioRenderClientForLoopback;
|
||||
winrt::com_ptr<IAudioCaptureClient> _audioCaptureClient;
|
||||
|
||||
rtc::scoped_refptr<webrtc::AudioProcessing> _audioProcessing;
|
||||
std::unique_ptr<webrtc::AudioFrame> _capturedFrame;
|
||||
std::unique_ptr<webrtc::AudioFrame> _renderedFrame;
|
||||
|
||||
HANDLE _thread = nullptr;
|
||||
HANDLE _audioSamplesReadyEvent = nullptr;
|
||||
HANDLE _captureThreadShutdownEvent = nullptr;
|
||||
|
||||
UINT32 _bufferSizeFrames = 0;
|
||||
UINT32 _deviceFrameSize = 0;
|
||||
QByteArray _deviceBuffer;
|
||||
QByteArray _resampleBuffer;
|
||||
UINT32 _bufferOffset = 0;
|
||||
|
||||
double _queryPerformanceMultiplier = 0.;
|
||||
double _deviceFrequencyMultiplier = 0.;
|
||||
|
||||
SwrContext *_swrContext = nullptr;
|
||||
int _swrSrcSampleRate = 0;
|
||||
|
||||
bool _microphoneInitialized = false;
|
||||
bool _initialized = false;
|
||||
|
||||
bool _recordingInitialized = false;
|
||||
bool _recordingFailed = false;
|
||||
bool _recording = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webrtc::details
|
||||
195
Telegram/lib_webrtc/webrtc/webrtc_audio_input_tester.cpp
Normal file
195
Telegram/lib_webrtc/webrtc/webrtc_audio_input_tester.cpp
Normal 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
|
||||
31
Telegram/lib_webrtc/webrtc/webrtc_audio_input_tester.h
Normal file
31
Telegram/lib_webrtc/webrtc/webrtc_audio_input_tester.h
Normal 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
|
||||
56
Telegram/lib_webrtc/webrtc/webrtc_create_adm.cpp
Normal file
56
Telegram/lib_webrtc/webrtc/webrtc_create_adm.cpp
Normal 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
|
||||
42
Telegram/lib_webrtc/webrtc/webrtc_create_adm.h
Normal file
42
Telegram/lib_webrtc/webrtc/webrtc_create_adm.h
Normal 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
|
||||
99
Telegram/lib_webrtc/webrtc/webrtc_device_common.h
Normal file
99
Telegram/lib_webrtc/webrtc/webrtc_device_common.h
Normal 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
|
||||
136
Telegram/lib_webrtc/webrtc/webrtc_device_resolver.cpp
Normal file
136
Telegram/lib_webrtc/webrtc/webrtc_device_resolver.cpp
Normal 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
|
||||
53
Telegram/lib_webrtc/webrtc/webrtc_device_resolver.h
Normal file
53
Telegram/lib_webrtc/webrtc/webrtc_device_resolver.h
Normal 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
|
||||
396
Telegram/lib_webrtc/webrtc/webrtc_environment.cpp
Normal file
396
Telegram/lib_webrtc/webrtc/webrtc_environment.cpp
Normal 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
|
||||
136
Telegram/lib_webrtc/webrtc/webrtc_environment.h
Normal file
136
Telegram/lib_webrtc/webrtc/webrtc_environment.h
Normal 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
|
||||
18
Telegram/lib_webrtc/webrtc/webrtc_pch.h
Normal file
18
Telegram/lib_webrtc/webrtc/webrtc_pch.h
Normal 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"
|
||||
579
Telegram/lib_webrtc/webrtc/webrtc_video_track.cpp
Normal file
579
Telegram/lib_webrtc/webrtc/webrtc_video_track.cpp
Normal 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
|
||||
123
Telegram/lib_webrtc/webrtc/webrtc_video_track.h
Normal file
123
Telegram/lib_webrtc/webrtc/webrtc_video_track.h
Normal 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
|
||||
Reference in New Issue
Block a user