// // 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 #include #include #include #include #include #include #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 #endif // WEBRTC_MAC namespace tgcalls { namespace { #ifdef WEBRTC_MAC class CaptureScheduler { public: void runAsync(std::function method) { dispatch_async(dispatch_get_main_queue(), ^{ method(); }); } void runDelayed(int delayMs, std::function 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 method) { _thread->PostTask(std::move(method)); } void runDelayed(int delayMs, std::function 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 frame) override; void setOutput( std::shared_ptr> sink); void setSecondaryOutput( std::shared_ptr> sink); void setOnFatalError(std::function); void setOnPause(std::function); private: rtc::scoped_refptr i420_buffer_; std::shared_ptr> _sink; std::shared_ptr< rtc::VideoSinkInterface> _secondarySink; DesktopSize size_; std::function _onFatalError; std::function _onPause; }; class DesktopSourceRenderer { public: DesktopSourceRenderer( CaptureScheduler &scheduler, DesktopCaptureSource source, DesktopCaptureSourceData data); void start(); void stop(); void setOutput(std::shared_ptr> sink); void setSecondaryOutput(std::shared_ptr> sink); void loop(); void setOnFatalError(std::function); void setOnPause(std::function); private: CaptureScheduler &_scheduler; std::unique_ptr _capturer; SourceFrameCallbackImpl _callback; std::shared_ptr _timerGuard; std::function _onFatalError; std::function _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 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> sink) { _sink = std::move(sink); } void SourceFrameCallbackImpl::setOnFatalError(std::function error) { _onFatalError = error; } void SourceFrameCallbackImpl::setOnPause(std::function pause) { _onPause = pause; } void SourceFrameCallbackImpl::setSecondaryOutput( std::shared_ptr> 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( 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(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(_timerGuard); _scheduler.runDelayed(_delayMs, [=] { if (guard.lock()) { loop(); } }); } void DesktopSourceRenderer::setOnFatalError(std::function error) { if (_fatalError) { error(); } else { _onFatalError = std::move(error); } } void DesktopSourceRenderer::setOnPause(std::function pause) { if (_currentlyOnPause) { pause(true); } _onPause = std::move(pause); } void DesktopSourceRenderer::setOutput( std::shared_ptr> sink) { _callback.setOutput(std::move(sink)); } void DesktopSourceRenderer::setSecondaryOutput( std::shared_ptr> sink) { _callback.setSecondaryOutput(std::move(sink)); } } // namespace struct DesktopCaptureSourceHelper::Renderer { CaptureScheduler scheduler; std::unique_ptr 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->scheduler.runAsync([renderer = _renderer, source, data] { renderer->renderer = std::make_unique( renderer->scheduler, source, data); }); } DesktopCaptureSourceHelper::~DesktopCaptureSourceHelper() { _renderer->scheduler.runAsync([renderer = _renderer] { }); } void DesktopCaptureSourceHelper::setOutput( std::shared_ptr< rtc::VideoSinkInterface> sink) const { _renderer->scheduler.runAsync([renderer = _renderer, sink] { renderer->renderer->setOutput(sink); }); } void DesktopCaptureSourceHelper::setSecondaryOutput( std::shared_ptr< rtc::VideoSinkInterface> 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 error) const { _renderer->scheduler.runAsync([renderer = _renderer, error = error] { renderer->renderer->setOnFatalError(error); }); } void DesktopCaptureSourceHelper::setOnPause(std::function 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