Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,68 @@
//
// DesktopCaptureSource.m
// TgVoipWebrtc
//
// Created by Mikhail Filimonov on 29.12.2020.
// Copyright © 2020 Mikhail Filimonov. All rights reserved.
//
#include "tgcalls/desktop_capturer/DesktopCaptureSource.h"
namespace tgcalls {
std::string DesktopCaptureSourceData::cachedKey() const {
return std::to_string(aspectSize.width)
+ 'x'
+ std::to_string(aspectSize.height)
+ ':'
+ std::to_string(fps)
+ ':'
+ (captureMouse ? '1' : '0');
}
DesktopCaptureSource::DesktopCaptureSource(
long long uniqueId,
std::string title,
bool isWindow)
: _uniqueId(uniqueId)
, _title(std::move(title))
, _isWindow(isWindow) {
}
long long DesktopCaptureSource::uniqueId() const {
return _uniqueId;
}
bool DesktopCaptureSource::isWindow() const {
return _isWindow;
}
std::string DesktopCaptureSource::deviceIdKey() const {
return std::string("desktop_capturer_")
+ (_isWindow ? "window_" : "screen_")
+ std::to_string(uniqueId());
}
std::string DesktopCaptureSource::title() const {
return _isWindow ? _title : "Screen";
}
std::string DesktopCaptureSource::uniqueKey() const {
return std::to_string(_uniqueId)
+ ':'
+ (_isWindow ? "Window" : "Screen");
}
std::string DesktopCaptureSource::deviceIdKey() {
return static_cast<const DesktopCaptureSource*>(this)->deviceIdKey();
}
std::string DesktopCaptureSource::title() {
return static_cast<const DesktopCaptureSource*>(this)->title();
}
std::string DesktopCaptureSource::uniqueKey() {
return static_cast<const DesktopCaptureSource*>(this)->uniqueKey();
}
} // namespace tgcalls

View file

@ -0,0 +1,85 @@
//
// DesktopCaptureSource.h
// TgVoipWebrtc
//
// Created by Mikhail Filimonov on 29.12.2020.
// Copyright © 2020 Mikhail Filimonov. All rights reserved.
//
#ifndef TGCALLS_DESKTOP_CAPTURE_SOURCE_H__
#define TGCALLS_DESKTOP_CAPTURE_SOURCE_H__
#include <string>
#ifdef WEBRTC_WIN
// Compiler errors in conflicting Windows headers if not included here.
#include <winsock2.h>
#endif // WEBRTC_WIN
namespace tgcalls {
class VideoSource {
public:
virtual ~VideoSource() = default;
virtual std::string deviceIdKey() = 0;
virtual std::string title() = 0;
virtual std::string uniqueKey() = 0;
};
struct DesktopSize {
int width = 0;
int height = 0;
};
struct DesktopCaptureSourceData {
DesktopSize aspectSize;
double fps = 24.;
bool captureMouse = true;
std::string cachedKey() const;
};
class DesktopCaptureSource : public VideoSource {
public:
DesktopCaptureSource(
long long uniqueId,
std::string title,
bool isWindow);
static DesktopCaptureSource Invalid() {
return InvalidTag{};
}
long long uniqueId() const;
bool isWindow() const;
std::string deviceIdKey() const;
std::string title() const;
std::string uniqueKey() const;
bool valid() const {
return _valid;
}
explicit operator bool() const {
return _valid;
}
private:
struct InvalidTag {};
DesktopCaptureSource(InvalidTag) : _valid(false) {
}
std::string deviceIdKey() override;
std::string title() override;
std::string uniqueKey() override;
long long _uniqueId = 0;
std::string _title;
bool _isWindow = false;
bool _valid = true;
};
} // namespace tgcalls
#endif // TGCALLS_DESKTOP_CAPTURE_SOURCE_H__

View file

@ -0,0 +1,461 @@
//
// DesktopCaptureSourceHelper.m
// TgVoipWebrtc
//
// Created by Mikhail Filimonov on 28.12.2020.
// Copyright © 2020 Mikhail Filimonov. All rights reserved.
//
#include "tgcalls/desktop_capturer/DesktopCaptureSourceHelper.h"
#include <iostream>
#include <memory>
#include <algorithm>
#include <chrono>
#include <iostream>
#include <vector>
#include <functional>
#include "tgcalls/desktop_capturer/DesktopCaptureSourceManager.h"
#include "rtc_base/thread.h"
#include "api/video/video_sink_interface.h"
#include "api/video/video_frame.h"
#include "modules/desktop_capture/desktop_and_cursor_composer.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "system_wrappers/include/clock.h"
#include "api/video/i420_buffer.h"
#include "third_party/libyuv/include/libyuv.h"
#ifdef WEBRTC_MAC
#import <QuartzCore/QuartzCore.h>
#endif // WEBRTC_MAC
namespace tgcalls {
namespace {
#ifdef WEBRTC_MAC
class CaptureScheduler {
public:
void runAsync(std::function<void()> method) {
dispatch_async(dispatch_get_main_queue(), ^{
method();
});
}
void runDelayed(int delayMs, std::function<void()> method) {
const auto time = dispatch_time(
DISPATCH_TIME_NOW,
((long long)delayMs * NSEC_PER_SEC) / 1000);
dispatch_after(time, dispatch_get_main_queue(), ^{
method();
});
}
};
#else // WEBRTC_MAC
rtc::Thread *GlobalCapturerThread() {
static auto result = [] {
auto thread = rtc::Thread::Create();
thread->SetName("WebRTC-DesktopCapturer", nullptr);
thread->Start();
return thread;
}();
return result.get();
}
class CaptureScheduler {
public:
CaptureScheduler() : _thread(GlobalCapturerThread()) {
}
void runAsync(std::function<void()> method) {
_thread->PostTask(std::move(method));
}
void runDelayed(int delayMs, std::function<void()> method) {
_thread->PostDelayedTask(std::move(method), webrtc::TimeDelta::Millis(delayMs));
}
private:
rtc::Thread *_thread;
};
#endif // WEBRTC_MAC
class SourceFrameCallbackImpl : public webrtc::DesktopCapturer::Callback {
public:
SourceFrameCallbackImpl(DesktopSize size, int fps);
void OnCaptureResult(
webrtc::DesktopCapturer::Result result,
std::unique_ptr<webrtc::DesktopFrame> frame) override;
void setOutput(
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
void setSecondaryOutput(
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
void setOnFatalError(std::function<void ()>);
void setOnPause(std::function<void (bool)>);
private:
rtc::scoped_refptr<webrtc::I420Buffer> i420_buffer_;
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> _sink;
std::shared_ptr<
rtc::VideoSinkInterface<webrtc::VideoFrame>> _secondarySink;
DesktopSize size_;
std::function<void ()> _onFatalError;
std::function<void (bool)> _onPause;
};
class DesktopSourceRenderer {
public:
DesktopSourceRenderer(
CaptureScheduler &scheduler,
DesktopCaptureSource source,
DesktopCaptureSourceData data);
void start();
void stop();
void setOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
void setSecondaryOutput(std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink);
void loop();
void setOnFatalError(std::function<void ()>);
void setOnPause(std::function<void (bool)>);
private:
CaptureScheduler &_scheduler;
std::unique_ptr<webrtc::DesktopCapturer> _capturer;
SourceFrameCallbackImpl _callback;
std::shared_ptr<bool> _timerGuard;
std::function<void()> _onFatalError;
std::function<void(bool)> _onPause;
bool _isRunning = false;
bool _fatalError = false;
bool _currentlyOnPause = false;
double _delayMs = 0.;
};
SourceFrameCallbackImpl::SourceFrameCallbackImpl(DesktopSize size, int fps)
: size_(size) {
}
void SourceFrameCallbackImpl::OnCaptureResult(
webrtc::DesktopCapturer::Result result,
std::unique_ptr<webrtc::DesktopFrame> frame) {
const auto failed = (result != webrtc::DesktopCapturer::Result::SUCCESS)
|| !frame
|| frame->size().equals({ 1, 1 });
if (failed) {
if (result == webrtc::DesktopCapturer::Result::ERROR_PERMANENT) {
if (_onFatalError) {
_onFatalError();
}
} else if (_onPause) {
_onPause(true);
}
return;
} else if (_onPause) {
_onPause(false);
}
const auto frameSize = frame->size();
auto fittedSize = (frameSize.width() >= size_.width * 2
|| frameSize.height() >= size_.height * 2)
? DesktopSize{ frameSize.width() / 2, frameSize.height() / 2 }
: DesktopSize{ frameSize.width(), frameSize.height() };
fittedSize.width -= (fittedSize.width % 4);
fittedSize.height -= (fittedSize.height % 4);
const auto outputSize = webrtc::DesktopSize{
fittedSize.width,
fittedSize.height
};
webrtc::BasicDesktopFrame outputFrame{ outputSize };
const auto outputRect = webrtc::DesktopRect::MakeSize(outputSize);
const auto outputRectData = outputFrame.data() +
outputFrame.stride() * outputRect.top() +
webrtc::DesktopFrame::kBytesPerPixel * outputRect.left();
libyuv::ARGBScale(
frame->data(),
frame->stride(),
frame->size().width(),
frame->size().height(),
outputRectData,
outputFrame.stride(),
outputSize.width(),
outputSize.height(),
libyuv::kFilterBilinear);
int width = outputFrame.size().width();
int height = outputFrame.size().height();
int stride_y = width;
int stride_uv = (width + 1) / 2;
if (!i420_buffer_
|| i420_buffer_->width() != width
|| i420_buffer_->height() != height) {
i420_buffer_ = webrtc::I420Buffer::Create(
width,
height,
stride_y,
stride_uv,
stride_uv);
}
int i420Result = libyuv::ConvertToI420(
outputFrame.data(),
width * height,
i420_buffer_->MutableDataY(), i420_buffer_->StrideY(),
i420_buffer_->MutableDataU(), i420_buffer_->StrideU(),
i420_buffer_->MutableDataV(), i420_buffer_->StrideV(),
0, 0,
width, height,
width, height,
libyuv::kRotate0,
libyuv::FOURCC_ARGB);
assert(i420Result == 0);
(void)i420Result;
webrtc::VideoFrame nativeVideoFrame = webrtc::VideoFrame(
i420_buffer_,
webrtc::kVideoRotation_0,
webrtc::Clock::GetRealTimeClock()->CurrentTime().us());
if (const auto sink = _sink.get()) {
_sink->OnFrame(nativeVideoFrame);
}
if (const auto sink = _secondarySink.get()) {
sink->OnFrame(nativeVideoFrame);
}
}
void SourceFrameCallbackImpl::setOutput(
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
_sink = std::move(sink);
}
void SourceFrameCallbackImpl::setOnFatalError(std::function<void ()> error) {
_onFatalError = error;
}
void SourceFrameCallbackImpl::setOnPause(std::function<void (bool)> pause) {
_onPause = pause;
}
void SourceFrameCallbackImpl::setSecondaryOutput(
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
_secondarySink = std::move(sink);
}
DesktopSourceRenderer::DesktopSourceRenderer(
CaptureScheduler &scheduler,
DesktopCaptureSource source,
DesktopCaptureSourceData data)
: _scheduler(scheduler)
, _callback(data.aspectSize, data.fps)
, _delayMs(1000. / data.fps) {
_callback.setOnFatalError([=] {
stop();
_fatalError = true;
if (_onFatalError) _onFatalError();
});
_callback.setOnPause([=] (bool pause) {
bool previousOnPause = _currentlyOnPause;
_currentlyOnPause = pause;
if (previousOnPause != _currentlyOnPause) {
if (_onPause) _onPause(pause);
}
});
auto options = webrtc::DesktopCaptureOptions::CreateDefault();
options.set_disable_effects(true);
options.set_detect_updated_region(true);
#ifdef WEBRTC_WIN
options.set_allow_directx_capturer(true);
#elif defined WEBRTC_MAC
options.set_allow_iosurface(true);
#elif defined WEBRTC_USE_PIPEWIRE
options.set_allow_pipewire(true);
#endif // WEBRTC_WIN || WEBRTC_MAC
_capturer = webrtc::DesktopCapturer::CreateGenericCapturer(options);
if (!_capturer) {
if (source.isWindow()) {
_capturer = webrtc::DesktopCapturer::CreateWindowCapturer(options);
} else {
_capturer = webrtc::DesktopCapturer::CreateScreenCapturer(options);
}
if (!_capturer) {
_fatalError = true;
return;
}
}
if (data.captureMouse) {
_capturer = std::make_unique<webrtc::DesktopAndCursorComposer>(
std::move(_capturer),
options);
}
_capturer->SelectSource(source.uniqueId());
_capturer->Start(&_callback);
}
void DesktopSourceRenderer::start() {
if (!_capturer || _isRunning) {
return;
}
// ++GlobalCount;
//#ifdef WEBRTC_MAC
// NSLog(@"current capture count: %d", GlobalCount);
//#endif // WEBRTC_MAC
_isRunning = true;
_timerGuard = std::make_shared<bool>(true);
loop();
}
void DesktopSourceRenderer::stop() {
// if (_isRunning) {
// GlobalCount--;
//
//#ifdef WEBRTC_MAC
// NSLog(@"current capture count: %d", GlobalCount);
//#endif // WEBRTC_MAC
// }
_isRunning = false;
_timerGuard = nullptr;
}
void DesktopSourceRenderer::loop() {
if (!_capturer || !_isRunning) {
return;
}
_capturer->CaptureFrame();
const auto guard = std::weak_ptr<bool>(_timerGuard);
_scheduler.runDelayed(_delayMs, [=] {
if (guard.lock()) {
loop();
}
});
}
void DesktopSourceRenderer::setOnFatalError(std::function<void ()> error) {
if (_fatalError) {
error();
} else {
_onFatalError = std::move(error);
}
}
void DesktopSourceRenderer::setOnPause(std::function<void (bool)> pause) {
if (_currentlyOnPause) {
pause(true);
}
_onPause = std::move(pause);
}
void DesktopSourceRenderer::setOutput(
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
_callback.setOutput(std::move(sink));
}
void DesktopSourceRenderer::setSecondaryOutput(
std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) {
_callback.setSecondaryOutput(std::move(sink));
}
} // namespace
struct DesktopCaptureSourceHelper::Renderer {
CaptureScheduler scheduler;
std::unique_ptr<DesktopSourceRenderer> renderer;
};
DesktopCaptureSource DesktopCaptureSourceForKey(
const std::string &uniqueKey) {
if (!ShouldBeDesktopCapture(uniqueKey)) {
return DesktopCaptureSource::Invalid();
}
if (uniqueKey == "desktop_capturer_pipewire") {
return DesktopCaptureSource(0, "pipewire", false);
}
const auto windowPrefix = std::string("desktop_capturer_window_");
const auto isWindow = (uniqueKey.find(windowPrefix) == 0);
DesktopCaptureSourceManager manager(isWindow
? DesktopCaptureType::Window
: DesktopCaptureType::Screen);
const auto sources = manager.sources();
// "desktop_capturer_window_".size() == "desktop_capturer_screen_".size()
const auto keyId = std::stoll(uniqueKey.substr(windowPrefix.size()));
for (const auto &source : sources) {
if (source.uniqueId() == keyId) {
return source;
}
}
return DesktopCaptureSource::Invalid();
}
bool ShouldBeDesktopCapture(const std::string &uniqueKey) {
return (uniqueKey.find("desktop_capturer_") == 0);
}
DesktopCaptureSourceHelper::DesktopCaptureSourceHelper(
DesktopCaptureSource source,
DesktopCaptureSourceData data)
: _renderer(std::make_shared<Renderer>()) {
_renderer->scheduler.runAsync([renderer = _renderer, source, data] {
renderer->renderer = std::make_unique<DesktopSourceRenderer>(
renderer->scheduler,
source,
data);
});
}
DesktopCaptureSourceHelper::~DesktopCaptureSourceHelper() {
_renderer->scheduler.runAsync([renderer = _renderer] {
});
}
void DesktopCaptureSourceHelper::setOutput(
std::shared_ptr<
rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) const {
_renderer->scheduler.runAsync([renderer = _renderer, sink] {
renderer->renderer->setOutput(sink);
});
}
void DesktopCaptureSourceHelper::setSecondaryOutput(
std::shared_ptr<
rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) const {
_renderer->scheduler.runAsync([renderer = _renderer, sink] {
renderer->renderer->setSecondaryOutput(sink);
});
}
void DesktopCaptureSourceHelper::start() const {
_renderer->scheduler.runAsync([renderer = _renderer] {
renderer->renderer->start();
});
}
void DesktopCaptureSourceHelper::setOnFatalError(std::function<void ()> error) const {
_renderer->scheduler.runAsync([renderer = _renderer, error = error] {
renderer->renderer->setOnFatalError(error);
});
}
void DesktopCaptureSourceHelper::setOnPause(std::function<void (bool)> pause) const {
_renderer->scheduler.runAsync([renderer = _renderer, pause = pause] {
renderer->renderer->setOnPause(pause);
});
}
void DesktopCaptureSourceHelper::stop() const {
_renderer->scheduler.runAsync([renderer = _renderer] {
renderer->renderer->stop();
});
}
} // namespace tgcalls

View file

@ -0,0 +1,56 @@
//
// DesktopCaptureSourceHelper.h
// TgVoipWebrtc
//
// Created by Mikhail Filimonov on 28.12.2020.
// Copyright © 2020 Mikhail Filimonov. All rights reserved.
//
#ifndef TGCALLS_DESKTOP_CAPTURE_SOURCE_HELPER_H__
#define TGCALLS_DESKTOP_CAPTURE_SOURCE_HELPER_H__
#include "tgcalls/desktop_capturer/DesktopCaptureSource.h"
#include <memory>
#include <functional>
namespace webrtc {
class VideoFrame;
} // namespace webrtc
namespace rtc {
template <typename T>
class VideoSinkInterface;
} // namespace rtc
namespace tgcalls {
DesktopCaptureSource DesktopCaptureSourceForKey(
const std::string &uniqueKey);
bool ShouldBeDesktopCapture(const std::string &uniqueKey);
class DesktopCaptureSourceHelper {
public:
DesktopCaptureSourceHelper(
DesktopCaptureSource source,
DesktopCaptureSourceData data);
~DesktopCaptureSourceHelper();
void setOutput(
std::shared_ptr<
rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) const;
void setSecondaryOutput(
std::shared_ptr<
rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) const;
void start() const;
void stop() const;
void setOnFatalError(std::function<void ()>) const;
void setOnPause(std::function<void (bool)>) const;
private:
struct Renderer;
std::shared_ptr<Renderer> _renderer;
};
} // namespace tgcalls
#endif // TGCALLS_DESKTOP_CAPTURE_SOURCE_HELPER_H__

View file

@ -0,0 +1,65 @@
//
// DesktopCaptureSourceManager.m
// TgVoipWebrtc
//
// Created by Mikhail Filimonov on 28.12.2020.
// Copyright © 2020 Mikhail Filimonov. All rights reserved.
//
#include "tgcalls/desktop_capturer/DesktopCaptureSourceManager.h"
#include "modules/desktop_capture/desktop_and_cursor_composer.h"
#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h"
#include "third_party/libyuv/include/libyuv.h"
#include "api/video/i420_buffer.h"
#include "tgcalls/desktop_capturer/DesktopCaptureSourceHelper.h"
namespace tgcalls {
DesktopCaptureSourceManager::DesktopCaptureSourceManager(
DesktopCaptureType type)
: _capturer(CreateForType(type))
, _type(type) {
}
DesktopCaptureSourceManager::~DesktopCaptureSourceManager() = default;
webrtc::DesktopCaptureOptions DesktopCaptureSourceManager::OptionsForType(
DesktopCaptureType type) {
auto result = webrtc::DesktopCaptureOptions::CreateDefault();
#ifdef WEBRTC_WIN
result.set_allow_directx_capturer(true);
#elif defined WEBRTC_MAC
result.set_allow_iosurface(type == DesktopCaptureType::Screen);
#elif defined WEBRTC_USE_PIPEWIRE
result.set_allow_pipewire(true);
#endif // WEBRTC_WIN || WEBRTC_MAC
result.set_detect_updated_region(true);
return result;
}
auto DesktopCaptureSourceManager::CreateForType(DesktopCaptureType type)
-> std::unique_ptr<webrtc::DesktopCapturer> {
const auto options = OptionsForType(type);
if (auto result = webrtc::DesktopCapturer::CreateGenericCapturer(options)) {
return result;
}
return (type == DesktopCaptureType::Screen)
? webrtc::DesktopCapturer::CreateScreenCapturer(options)
: webrtc::DesktopCapturer::CreateWindowCapturer(options);
}
std::vector<DesktopCaptureSource> DesktopCaptureSourceManager::sources() {
auto result = std::vector<DesktopCaptureSource>();
auto list = webrtc::DesktopCapturer::SourceList();
if (_capturer && _capturer->GetSourceList(&list)) {
const auto isWindow = (_type == DesktopCaptureType::Window);
for (const auto &source : list) {
result.emplace_back(source.id, source.title, isWindow);
}
}
return result;
}
} // namespace tgcalls

View file

@ -0,0 +1,49 @@
//
// DesktopCaptureSourceManager.h
// TgVoipWebrtc
//
// Created by Mikhail Filimonov on 28.12.2020.
// Copyright © 2020 Mikhail Filimonov. All rights reserved.
//
#ifndef TGCALLS_DESKTOP_CAPTURE_SOURCE_MANAGER_H__
#define TGCALLS_DESKTOP_CAPTURE_SOURCE_MANAGER_H__
#include "tgcalls/desktop_capturer/DesktopCaptureSource.h"
#include "tgcalls/desktop_capturer/DesktopCaptureSourceHelper.h"
#include <map>
#include <vector>
namespace webrtc {
class DesktopCapturer;
class DesktopCaptureOptions;
} // namespace webrtc
namespace tgcalls {
enum class DesktopCaptureType {
Screen,
Window,
};
class DesktopCaptureSourceManager {
public:
explicit DesktopCaptureSourceManager(DesktopCaptureType type);
~DesktopCaptureSourceManager();
std::vector<DesktopCaptureSource> sources();
private:
static webrtc::DesktopCaptureOptions OptionsForType(
DesktopCaptureType type);
static std::unique_ptr<webrtc::DesktopCapturer> CreateForType(
DesktopCaptureType type);
std::unique_ptr<webrtc::DesktopCapturer> _capturer;
DesktopCaptureType _type = DesktopCaptureType::Screen;
};
} // namespace tgcalls
#endif // TGCALLS_DESKTOP_CAPTURE_SOURCE_MANAGER_H__