Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -0,0 +1,2 @@
|
|||
alcooper@chromium.org
|
||||
mfoltz@chromium.org
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/blank_detector_desktop_capturer_wrapper.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
BlankDetectorDesktopCapturerWrapper::BlankDetectorDesktopCapturerWrapper(
|
||||
std::unique_ptr<DesktopCapturer> capturer,
|
||||
RgbaColor blank_pixel,
|
||||
bool check_per_capture)
|
||||
: capturer_(std::move(capturer)),
|
||||
blank_pixel_(blank_pixel),
|
||||
check_per_capture_(check_per_capture) {
|
||||
RTC_DCHECK(capturer_);
|
||||
}
|
||||
|
||||
BlankDetectorDesktopCapturerWrapper::~BlankDetectorDesktopCapturerWrapper() =
|
||||
default;
|
||||
|
||||
void BlankDetectorDesktopCapturerWrapper::Start(
|
||||
DesktopCapturer::Callback* callback) {
|
||||
callback_ = callback;
|
||||
capturer_->Start(this);
|
||||
}
|
||||
|
||||
void BlankDetectorDesktopCapturerWrapper::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory));
|
||||
}
|
||||
|
||||
void BlankDetectorDesktopCapturerWrapper::CaptureFrame() {
|
||||
RTC_DCHECK(callback_);
|
||||
capturer_->CaptureFrame();
|
||||
}
|
||||
|
||||
void BlankDetectorDesktopCapturerWrapper::SetExcludedWindow(WindowId window) {
|
||||
capturer_->SetExcludedWindow(window);
|
||||
}
|
||||
|
||||
bool BlankDetectorDesktopCapturerWrapper::GetSourceList(SourceList* sources) {
|
||||
return capturer_->GetSourceList(sources);
|
||||
}
|
||||
|
||||
bool BlankDetectorDesktopCapturerWrapper::SelectSource(SourceId id) {
|
||||
if (check_per_capture_) {
|
||||
// If we start capturing a new source, we must reset these members
|
||||
// so we don't short circuit the blank detection logic.
|
||||
is_first_frame_ = true;
|
||||
non_blank_frame_received_ = false;
|
||||
}
|
||||
|
||||
return capturer_->SelectSource(id);
|
||||
}
|
||||
|
||||
bool BlankDetectorDesktopCapturerWrapper::FocusOnSelectedSource() {
|
||||
return capturer_->FocusOnSelectedSource();
|
||||
}
|
||||
|
||||
bool BlankDetectorDesktopCapturerWrapper::IsOccluded(const DesktopVector& pos) {
|
||||
return capturer_->IsOccluded(pos);
|
||||
}
|
||||
|
||||
void BlankDetectorDesktopCapturerWrapper::OnCaptureResult(
|
||||
Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) {
|
||||
RTC_DCHECK(callback_);
|
||||
if (result != Result::SUCCESS || non_blank_frame_received_) {
|
||||
callback_->OnCaptureResult(result, std::move(frame));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
// Capturer can call the blank detector with empty frame. Blank
|
||||
// detector regards it as a blank frame.
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY,
|
||||
std::unique_ptr<DesktopFrame>());
|
||||
return;
|
||||
}
|
||||
|
||||
// If nothing has been changed in current frame, we do not need to check it
|
||||
// again.
|
||||
if (!frame->updated_region().is_empty() || is_first_frame_) {
|
||||
last_frame_is_blank_ = IsBlankFrame(*frame);
|
||||
is_first_frame_ = false;
|
||||
}
|
||||
RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.BlankFrameDetected",
|
||||
last_frame_is_blank_);
|
||||
if (!last_frame_is_blank_) {
|
||||
non_blank_frame_received_ = true;
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
|
||||
return;
|
||||
}
|
||||
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY,
|
||||
std::unique_ptr<DesktopFrame>());
|
||||
}
|
||||
|
||||
bool BlankDetectorDesktopCapturerWrapper::IsBlankFrame(
|
||||
const DesktopFrame& frame) const {
|
||||
// We will check 7489 pixels for a frame with 1024 x 768 resolution.
|
||||
for (int i = 0; i < frame.size().width() * frame.size().height(); i += 105) {
|
||||
const int x = i % frame.size().width();
|
||||
const int y = i / frame.size().width();
|
||||
if (!IsBlankPixel(frame, x, y)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We are verifying the pixel in the center as well.
|
||||
return IsBlankPixel(frame, frame.size().width() / 2,
|
||||
frame.size().height() / 2);
|
||||
}
|
||||
|
||||
bool BlankDetectorDesktopCapturerWrapper::IsBlankPixel(
|
||||
const DesktopFrame& frame,
|
||||
int x,
|
||||
int y) const {
|
||||
uint8_t* pixel_data = frame.GetFrameDataAtPos(DesktopVector(x, y));
|
||||
return RgbaColor(pixel_data) == blank_pixel_;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_BLANK_DETECTOR_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_BLANK_DETECTOR_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/rgba_color.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A DesktopCapturer wrapper detects the return value of its owned
|
||||
// DesktopCapturer implementation. If sampled pixels returned by the
|
||||
// DesktopCapturer implementation all equal to the blank pixel, this wrapper
|
||||
// returns ERROR_TEMPORARY. If the DesktopCapturer implementation fails for too
|
||||
// many times, this wrapper returns ERROR_PERMANENT.
|
||||
class BlankDetectorDesktopCapturerWrapper final
|
||||
: public DesktopCapturer,
|
||||
public DesktopCapturer::Callback {
|
||||
public:
|
||||
// Creates BlankDetectorDesktopCapturerWrapper. BlankDesktopCapturerWrapper
|
||||
// takes ownership of `capturer`. The `blank_pixel` is the unmodified color
|
||||
// returned by the `capturer`.
|
||||
BlankDetectorDesktopCapturerWrapper(std::unique_ptr<DesktopCapturer> capturer,
|
||||
RgbaColor blank_pixel,
|
||||
bool check_per_capture = false);
|
||||
~BlankDetectorDesktopCapturerWrapper() override;
|
||||
|
||||
// DesktopCapturer interface.
|
||||
void Start(DesktopCapturer::Callback* callback) override;
|
||||
void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
||||
void CaptureFrame() override;
|
||||
void SetExcludedWindow(WindowId window) override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
bool FocusOnSelectedSource() override;
|
||||
bool IsOccluded(const DesktopVector& pos) override;
|
||||
|
||||
private:
|
||||
// DesktopCapturer::Callback interface.
|
||||
void OnCaptureResult(Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) override;
|
||||
|
||||
bool IsBlankFrame(const DesktopFrame& frame) const;
|
||||
|
||||
// Detects whether pixel at (x, y) equals to `blank_pixel_`.
|
||||
bool IsBlankPixel(const DesktopFrame& frame, int x, int y) const;
|
||||
|
||||
const std::unique_ptr<DesktopCapturer> capturer_;
|
||||
const RgbaColor blank_pixel_;
|
||||
|
||||
// Whether a non-blank frame has been received.
|
||||
bool non_blank_frame_received_ = false;
|
||||
|
||||
// Whether the last frame is blank.
|
||||
bool last_frame_is_blank_ = false;
|
||||
|
||||
// Whether current frame is the first frame.
|
||||
bool is_first_frame_ = true;
|
||||
|
||||
// Blank inspection is made per capture instead of once for all
|
||||
// screens or windows.
|
||||
bool check_per_capture_ = false;
|
||||
|
||||
DesktopCapturer::Callback* callback_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_BLANK_DETECTOR_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/cropped_desktop_frame.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A DesktopFrame that is a sub-rect of another DesktopFrame.
|
||||
class CroppedDesktopFrame : public DesktopFrame {
|
||||
public:
|
||||
CroppedDesktopFrame(std::unique_ptr<DesktopFrame> frame,
|
||||
const DesktopRect& rect);
|
||||
|
||||
CroppedDesktopFrame(const CroppedDesktopFrame&) = delete;
|
||||
CroppedDesktopFrame& operator=(const CroppedDesktopFrame&) = delete;
|
||||
|
||||
private:
|
||||
const std::unique_ptr<DesktopFrame> frame_;
|
||||
};
|
||||
|
||||
std::unique_ptr<DesktopFrame> CreateCroppedDesktopFrame(
|
||||
std::unique_ptr<DesktopFrame> frame,
|
||||
const DesktopRect& rect) {
|
||||
RTC_DCHECK(frame);
|
||||
|
||||
DesktopRect intersection = DesktopRect::MakeSize(frame->size());
|
||||
intersection.IntersectWith(rect);
|
||||
if (intersection.is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (frame->size().equals(rect.size())) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
return std::unique_ptr<DesktopFrame>(
|
||||
new CroppedDesktopFrame(std::move(frame), intersection));
|
||||
}
|
||||
|
||||
CroppedDesktopFrame::CroppedDesktopFrame(std::unique_ptr<DesktopFrame> frame,
|
||||
const DesktopRect& rect)
|
||||
: DesktopFrame(rect.size(),
|
||||
frame->stride(),
|
||||
frame->GetFrameDataAtPos(rect.top_left()),
|
||||
frame->shared_memory()),
|
||||
frame_(std::move(frame)) {
|
||||
MoveFrameInfoFrom(frame_.get());
|
||||
set_top_left(frame_->top_left().add(rect.top_left()));
|
||||
mutable_updated_region()->IntersectWith(rect);
|
||||
mutable_updated_region()->Translate(-rect.left(), -rect.top());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Creates a DesktopFrame to contain only the area of `rect` in the original
|
||||
// `frame`.
|
||||
// `frame` should not be nullptr. `rect` is in `frame` coordinate, i.e.
|
||||
// `frame`->top_left() does not impact the area of `rect`.
|
||||
// Returns nullptr frame if `rect` is not contained by the bounds of `frame`.
|
||||
std::unique_ptr<DesktopFrame> RTC_EXPORT
|
||||
CreateCroppedDesktopFrame(std::unique_ptr<DesktopFrame> frame,
|
||||
const DesktopRect& rect);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_CROPPED_DESKTOP_FRAME_H_
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/cropping_window_capturer.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/cropped_desktop_frame.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
CroppingWindowCapturer::CroppingWindowCapturer(
|
||||
const DesktopCaptureOptions& options)
|
||||
: options_(options),
|
||||
callback_(NULL),
|
||||
window_capturer_(DesktopCapturer::CreateRawWindowCapturer(options)),
|
||||
selected_window_(kNullWindowId),
|
||||
excluded_window_(kNullWindowId) {}
|
||||
|
||||
CroppingWindowCapturer::~CroppingWindowCapturer() {}
|
||||
|
||||
void CroppingWindowCapturer::Start(DesktopCapturer::Callback* callback) {
|
||||
callback_ = callback;
|
||||
window_capturer_->Start(callback);
|
||||
}
|
||||
|
||||
void CroppingWindowCapturer::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
window_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory));
|
||||
}
|
||||
|
||||
void CroppingWindowCapturer::CaptureFrame() {
|
||||
if (ShouldUseScreenCapturer()) {
|
||||
if (!screen_capturer_.get()) {
|
||||
screen_capturer_ = DesktopCapturer::CreateRawScreenCapturer(options_);
|
||||
if (excluded_window_) {
|
||||
screen_capturer_->SetExcludedWindow(excluded_window_);
|
||||
}
|
||||
screen_capturer_->Start(this);
|
||||
}
|
||||
screen_capturer_->CaptureFrame();
|
||||
} else {
|
||||
window_capturer_->CaptureFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void CroppingWindowCapturer::SetExcludedWindow(WindowId window) {
|
||||
excluded_window_ = window;
|
||||
if (screen_capturer_.get()) {
|
||||
screen_capturer_->SetExcludedWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
bool CroppingWindowCapturer::GetSourceList(SourceList* sources) {
|
||||
return window_capturer_->GetSourceList(sources);
|
||||
}
|
||||
|
||||
bool CroppingWindowCapturer::SelectSource(SourceId id) {
|
||||
if (window_capturer_->SelectSource(id)) {
|
||||
selected_window_ = id;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CroppingWindowCapturer::FocusOnSelectedSource() {
|
||||
return window_capturer_->FocusOnSelectedSource();
|
||||
}
|
||||
|
||||
void CroppingWindowCapturer::OnCaptureResult(
|
||||
DesktopCapturer::Result result,
|
||||
std::unique_ptr<DesktopFrame> screen_frame) {
|
||||
if (!ShouldUseScreenCapturer()) {
|
||||
RTC_LOG(LS_INFO) << "Window no longer on top when ScreenCapturer finishes";
|
||||
window_capturer_->CaptureFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != Result::SUCCESS) {
|
||||
RTC_LOG(LS_WARNING) << "ScreenCapturer failed to capture a frame";
|
||||
callback_->OnCaptureResult(result, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
DesktopRect window_rect = GetWindowRectInVirtualScreen();
|
||||
if (window_rect.is_empty()) {
|
||||
RTC_LOG(LS_WARNING) << "Window rect is empty";
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> cropped_frame =
|
||||
CreateCroppedDesktopFrame(std::move(screen_frame), window_rect);
|
||||
|
||||
if (!cropped_frame) {
|
||||
RTC_LOG(LS_WARNING) << "Window is outside of the captured display";
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(cropped_frame));
|
||||
}
|
||||
|
||||
bool CroppingWindowCapturer::IsOccluded(const DesktopVector& pos) {
|
||||
// Returns true if either capturer returns true.
|
||||
if (window_capturer_->IsOccluded(pos)) {
|
||||
return true;
|
||||
}
|
||||
if (screen_capturer_ != nullptr && screen_capturer_->IsOccluded(pos)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !defined(WEBRTC_WIN)
|
||||
// CroppingWindowCapturer is implemented only for windows. On other platforms
|
||||
// the regular window capturer is used.
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer(
|
||||
const DesktopCaptureOptions& options) {
|
||||
return DesktopCapturer::CreateWindowCapturer(options);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// WindowCapturer implementation that uses a screen capturer to capture the
|
||||
// whole screen and crops the video frame to the window area when the captured
|
||||
// window is on top.
|
||||
class RTC_EXPORT CroppingWindowCapturer : public DesktopCapturer,
|
||||
public DesktopCapturer::Callback {
|
||||
public:
|
||||
static std::unique_ptr<DesktopCapturer> CreateCapturer(
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
~CroppingWindowCapturer() override;
|
||||
|
||||
// DesktopCapturer implementation.
|
||||
void Start(DesktopCapturer::Callback* callback) override;
|
||||
void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
||||
void CaptureFrame() override;
|
||||
void SetExcludedWindow(WindowId window) override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
bool FocusOnSelectedSource() override;
|
||||
bool IsOccluded(const DesktopVector& pos) override;
|
||||
|
||||
// DesktopCapturer::Callback implementation, passed to `screen_capturer_` to
|
||||
// intercept the capture result.
|
||||
void OnCaptureResult(DesktopCapturer::Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) override;
|
||||
|
||||
protected:
|
||||
explicit CroppingWindowCapturer(const DesktopCaptureOptions& options);
|
||||
|
||||
// The platform implementation should override these methods.
|
||||
|
||||
// Returns true if it is OK to capture the whole screen and crop to the
|
||||
// selected window, i.e. the selected window is opaque, rectangular, and not
|
||||
// occluded.
|
||||
virtual bool ShouldUseScreenCapturer() = 0;
|
||||
|
||||
// Returns the window area relative to the top left of the virtual screen
|
||||
// within the bounds of the virtual screen. This function should return the
|
||||
// DesktopRect in full desktop coordinates, i.e. the top-left monitor starts
|
||||
// from (0, 0).
|
||||
virtual DesktopRect GetWindowRectInVirtualScreen() = 0;
|
||||
|
||||
WindowId selected_window() const { return selected_window_; }
|
||||
WindowId excluded_window() const { return excluded_window_; }
|
||||
DesktopCapturer* window_capturer() const { return window_capturer_.get(); }
|
||||
|
||||
private:
|
||||
DesktopCaptureOptions options_;
|
||||
DesktopCapturer::Callback* callback_;
|
||||
std::unique_ptr<DesktopCapturer> window_capturer_;
|
||||
std::unique_ptr<DesktopCapturer> screen_capturer_;
|
||||
SourceId selected_window_;
|
||||
WindowId excluded_window_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_CROPPING_WINDOW_CAPTURER_H_
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/cropping_window_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h"
|
||||
#include "modules/desktop_capture/win/screen_capture_utils.h"
|
||||
#include "modules/desktop_capture/win/selected_window_context.h"
|
||||
#include "modules/desktop_capture/win/window_capture_utils.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/trace_event.h"
|
||||
#include "rtc_base/win/windows_version.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Used to pass input data for verifying the selected window is on top.
|
||||
struct TopWindowVerifierContext : public SelectedWindowContext {
|
||||
TopWindowVerifierContext(HWND selected_window,
|
||||
HWND excluded_window,
|
||||
DesktopRect selected_window_rect,
|
||||
WindowCaptureHelperWin* window_capture_helper)
|
||||
: SelectedWindowContext(selected_window,
|
||||
selected_window_rect,
|
||||
window_capture_helper),
|
||||
excluded_window(excluded_window) {
|
||||
RTC_DCHECK_NE(selected_window, excluded_window);
|
||||
}
|
||||
|
||||
// Determines whether the selected window is on top (not occluded by any
|
||||
// windows except for those it owns or any excluded window).
|
||||
bool IsTopWindow() {
|
||||
if (!IsSelectedWindowValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enumerate all top-level windows above the selected window in Z-order,
|
||||
// checking whether any overlaps it. This uses FindWindowEx rather than
|
||||
// EnumWindows because the latter excludes certain system windows (e.g. the
|
||||
// Start menu & other taskbar menus) that should be detected here to avoid
|
||||
// inadvertent capture.
|
||||
int num_retries = 0;
|
||||
while (true) {
|
||||
HWND hwnd = nullptr;
|
||||
while ((hwnd = FindWindowEx(nullptr, hwnd, nullptr, nullptr))) {
|
||||
if (hwnd == selected_window()) {
|
||||
// Windows are enumerated in top-down Z-order, so we can stop
|
||||
// enumerating upon reaching the selected window & report it's on top.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ignore the excluded window.
|
||||
if (hwnd == excluded_window) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore windows that aren't visible on the current desktop.
|
||||
if (!window_capture_helper()->IsWindowVisibleOnCurrentDesktop(hwnd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore Chrome notification windows, especially the notification for
|
||||
// the ongoing window sharing. Notes:
|
||||
// - This only works with notifications from Chrome, not other Apps.
|
||||
// - All notifications from Chrome will be ignored.
|
||||
// - This may cause part or whole of notification window being cropped
|
||||
// into the capturing of the target window if there is overlapping.
|
||||
if (window_capture_helper()->IsWindowChromeNotification(hwnd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore windows owned by the selected window since we want to capture
|
||||
// them.
|
||||
if (IsWindowOwnedBySelectedWindow(hwnd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check whether this window intersects with the selected window.
|
||||
if (IsWindowOverlappingSelectedWindow(hwnd)) {
|
||||
// If intersection is not empty, the selected window is not on top.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD lastError = GetLastError();
|
||||
if (lastError == ERROR_SUCCESS) {
|
||||
// The enumeration completed successfully without finding the selected
|
||||
// window (which may have been closed).
|
||||
RTC_LOG(LS_WARNING) << "Failed to find selected window (only expected "
|
||||
"if it was closed)";
|
||||
RTC_DCHECK(!IsWindow(selected_window()));
|
||||
return false;
|
||||
} else if (lastError == ERROR_INVALID_WINDOW_HANDLE) {
|
||||
// This error may occur if a window is closed around the time it's
|
||||
// enumerated; retry the enumeration in this case up to 10 times
|
||||
// (this should be a rare race & unlikely to recur).
|
||||
if (++num_retries <= 10) {
|
||||
RTC_LOG(LS_WARNING) << "Enumeration failed due to race with a window "
|
||||
"closing; retrying - retry #"
|
||||
<< num_retries;
|
||||
continue;
|
||||
} else {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "Exhausted retry allowance around window enumeration failures "
|
||||
"due to races with windows closing";
|
||||
}
|
||||
}
|
||||
|
||||
// The enumeration failed with an unexpected error (or more repeats of
|
||||
// an infrequently-expected error than anticipated). After logging this &
|
||||
// firing an assert when enabled, report that the selected window isn't
|
||||
// topmost to avoid inadvertent capture of other windows.
|
||||
RTC_LOG(LS_ERROR) << "Failed to enumerate windows: " << lastError;
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const HWND excluded_window;
|
||||
};
|
||||
|
||||
class CroppingWindowCapturerWin : public CroppingWindowCapturer {
|
||||
public:
|
||||
explicit CroppingWindowCapturerWin(const DesktopCaptureOptions& options)
|
||||
: CroppingWindowCapturer(options),
|
||||
enumerate_current_process_windows_(
|
||||
options.enumerate_current_process_windows()),
|
||||
full_screen_window_detector_(options.full_screen_window_detector()) {}
|
||||
|
||||
void CaptureFrame() override;
|
||||
|
||||
private:
|
||||
bool ShouldUseScreenCapturer() override;
|
||||
DesktopRect GetWindowRectInVirtualScreen() override;
|
||||
|
||||
// Returns either selected by user sourceId or sourceId provided by
|
||||
// FullScreenWindowDetector
|
||||
WindowId GetWindowToCapture() const;
|
||||
|
||||
// The region from GetWindowRgn in the desktop coordinate if the region is
|
||||
// rectangular, or the rect from GetWindowRect if the region is not set.
|
||||
DesktopRect window_region_rect_;
|
||||
|
||||
WindowCaptureHelperWin window_capture_helper_;
|
||||
|
||||
bool enumerate_current_process_windows_;
|
||||
|
||||
rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_;
|
||||
|
||||
// Used to make sure that we only log the usage of fullscreen detection once.
|
||||
mutable bool fullscreen_usage_logged_ = false;
|
||||
};
|
||||
|
||||
void CroppingWindowCapturerWin::CaptureFrame() {
|
||||
DesktopCapturer* win_capturer = window_capturer();
|
||||
if (win_capturer) {
|
||||
// Feed the actual list of windows into full screen window detector.
|
||||
if (full_screen_window_detector_) {
|
||||
full_screen_window_detector_->UpdateWindowListIfNeeded(
|
||||
selected_window(), [this](DesktopCapturer::SourceList* sources) {
|
||||
// Get the list of top level windows, including ones with empty
|
||||
// title. win_capturer_->GetSourceList can't be used here
|
||||
// cause it filters out the windows with empty titles and
|
||||
// it uses responsiveness check which could lead to performance
|
||||
// issues.
|
||||
SourceList result;
|
||||
int window_list_flags =
|
||||
enumerate_current_process_windows_
|
||||
? GetWindowListFlags::kNone
|
||||
: GetWindowListFlags::kIgnoreCurrentProcessWindows;
|
||||
|
||||
if (!webrtc::GetWindowList(window_list_flags, &result))
|
||||
return false;
|
||||
|
||||
// Filter out windows not visible on current desktop
|
||||
auto it = std::remove_if(
|
||||
result.begin(), result.end(), [this](const auto& source) {
|
||||
HWND hwnd = reinterpret_cast<HWND>(source.id);
|
||||
return !window_capture_helper_
|
||||
.IsWindowVisibleOnCurrentDesktop(hwnd);
|
||||
});
|
||||
result.erase(it, result.end());
|
||||
|
||||
sources->swap(result);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
win_capturer->SelectSource(GetWindowToCapture());
|
||||
}
|
||||
|
||||
CroppingWindowCapturer::CaptureFrame();
|
||||
}
|
||||
|
||||
bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
|
||||
if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN8 &&
|
||||
window_capture_helper_.IsAeroEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HWND selected = reinterpret_cast<HWND>(GetWindowToCapture());
|
||||
// Check if the window is visible on current desktop.
|
||||
if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the window is a translucent layered window.
|
||||
const LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
|
||||
if (window_ex_style & WS_EX_LAYERED) {
|
||||
COLORREF color_ref_key = 0;
|
||||
BYTE alpha = 0;
|
||||
DWORD flags = 0;
|
||||
|
||||
// GetLayeredWindowAttributes fails if the window was setup with
|
||||
// UpdateLayeredWindow. We have no way to know the opacity of the window in
|
||||
// that case. This happens for Stiky Note (crbug/412726).
|
||||
if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
|
||||
return false;
|
||||
|
||||
// UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
|
||||
// the previous GetLayeredWindowAttributes to fail. So we only need to check
|
||||
// the window wide color key or alpha.
|
||||
if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!GetWindowRect(selected, &window_region_rect_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DesktopRect content_rect;
|
||||
if (!GetWindowContentRect(selected, &content_rect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DesktopRect region_rect;
|
||||
// Get the window region and check if it is rectangular.
|
||||
const int region_type =
|
||||
GetWindowRegionTypeWithBoundary(selected, ®ion_rect);
|
||||
|
||||
// Do not use the screen capturer if the region is empty or not rectangular.
|
||||
if (region_type == COMPLEXREGION || region_type == NULLREGION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (region_type == SIMPLEREGION) {
|
||||
// The `region_rect` returned from GetRgnBox() is always in window
|
||||
// coordinate.
|
||||
region_rect.Translate(window_region_rect_.left(),
|
||||
window_region_rect_.top());
|
||||
// MSDN: The window region determines the area *within* the window where the
|
||||
// system permits drawing.
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd144950(v=vs.85).aspx.
|
||||
//
|
||||
// `region_rect` should always be inside of `window_region_rect_`. So after
|
||||
// the intersection, `window_region_rect_` == `region_rect`. If so, what's
|
||||
// the point of the intersecting operations? Why cannot we directly retrieve
|
||||
// `window_region_rect_` from GetWindowRegionTypeWithBoundary() function?
|
||||
// TODO(zijiehe): Figure out the purpose of these intersections.
|
||||
window_region_rect_.IntersectWith(region_rect);
|
||||
content_rect.IntersectWith(region_rect);
|
||||
}
|
||||
|
||||
// Check if the client area is out of the screen area. When the window is
|
||||
// maximized, only its client area is visible in the screen, the border will
|
||||
// be hidden. So we are using `content_rect` here.
|
||||
if (!GetFullscreenRect().ContainsRect(content_rect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the window is occluded by any other window, excluding the child
|
||||
// windows, context menus, and `excluded_window_`.
|
||||
// `content_rect` is preferred, see the comments on
|
||||
// IsWindowIntersectWithSelectedWindow().
|
||||
TopWindowVerifierContext context(selected,
|
||||
reinterpret_cast<HWND>(excluded_window()),
|
||||
content_rect, &window_capture_helper_);
|
||||
return context.IsTopWindow();
|
||||
}
|
||||
|
||||
DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
|
||||
TRACE_EVENT0("webrtc",
|
||||
"CroppingWindowCapturerWin::GetWindowRectInVirtualScreen");
|
||||
DesktopRect window_rect;
|
||||
HWND hwnd = reinterpret_cast<HWND>(GetWindowToCapture());
|
||||
if (!GetCroppedWindowRect(hwnd, /*avoid_cropping_border*/ false, &window_rect,
|
||||
/*original_rect*/ nullptr)) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
|
||||
return window_rect;
|
||||
}
|
||||
window_rect.IntersectWith(window_region_rect_);
|
||||
|
||||
// Convert `window_rect` to be relative to the top-left of the virtual screen.
|
||||
DesktopRect screen_rect(GetFullscreenRect());
|
||||
window_rect.IntersectWith(screen_rect);
|
||||
window_rect.Translate(-screen_rect.left(), -screen_rect.top());
|
||||
return window_rect;
|
||||
}
|
||||
|
||||
WindowId CroppingWindowCapturerWin::GetWindowToCapture() const {
|
||||
const auto selected_source = selected_window();
|
||||
const auto full_screen_source =
|
||||
full_screen_window_detector_
|
||||
? full_screen_window_detector_->FindFullScreenWindow(selected_source)
|
||||
: 0;
|
||||
if (full_screen_source && full_screen_source != selected_source &&
|
||||
!fullscreen_usage_logged_) {
|
||||
fullscreen_usage_logged_ = true;
|
||||
LogDesktopCapturerFullscreenDetectorUsage();
|
||||
}
|
||||
return full_screen_source ? full_screen_source : selected_source;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer(
|
||||
const DesktopCaptureOptions& options) {
|
||||
std::unique_ptr<DesktopCapturer> capturer(
|
||||
new CroppingWindowCapturerWin(options));
|
||||
if (capturer && options.detect_updated_region()) {
|
||||
capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
|
||||
}
|
||||
|
||||
return capturer;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DELEGATED_SOURCE_LIST_CONTROLLER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DELEGATED_SOURCE_LIST_CONTROLLER_H_
|
||||
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A controller to be implemented and returned by
|
||||
// GetDelegatedSourceListController in capturers that require showing their own
|
||||
// source list and managing user selection there. Apart from ensuring the
|
||||
// visibility of the source list, these capturers should largely be interacted
|
||||
// with the same as a normal capturer, though there may be some caveats for
|
||||
// some DesktopCapturer methods. See GetDelegatedSourceListController for more
|
||||
// information.
|
||||
class RTC_EXPORT DelegatedSourceListController {
|
||||
public:
|
||||
// Notifications that can be used to help drive any UI that the consumer may
|
||||
// want to show around this source list (e.g. if an consumer shows their own
|
||||
// UI in addition to the delegated source list).
|
||||
class Observer {
|
||||
public:
|
||||
// Called after the user has made a selection in the delegated source list.
|
||||
// Note that the consumer will still need to get the source out of the
|
||||
// capturer by calling GetSourceList.
|
||||
virtual void OnSelection() = 0;
|
||||
|
||||
// Called when there is any user action that cancels the source selection.
|
||||
virtual void OnCancelled() = 0;
|
||||
|
||||
// Called when there is a system error that cancels the source selection.
|
||||
virtual void OnError() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Observer() {}
|
||||
};
|
||||
|
||||
// Observer must remain valid until the owning DesktopCapturer is destroyed.
|
||||
// Only one Observer is allowed at a time, and may be cleared by passing
|
||||
// nullptr.
|
||||
virtual void Observe(Observer* observer) = 0;
|
||||
|
||||
// Used to prompt the capturer to show the delegated source list. If the
|
||||
// source list is already visible, this will be a no-op. Must be called after
|
||||
// starting the DesktopCapturer.
|
||||
//
|
||||
// Note that any selection from a previous invocation of the source list may
|
||||
// be cleared when this method is called.
|
||||
virtual void EnsureVisible() = 0;
|
||||
|
||||
// Used to prompt the capturer to hide the delegated source list. If the
|
||||
// source list is already hidden, this will be a no-op.
|
||||
virtual void EnsureHidden() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~DelegatedSourceListController() {}
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DELEGATED_SOURCE_LIST_CONTROLLER_H_
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_and_cursor_composer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/mouse_cursor.h"
|
||||
#include "modules/desktop_capture/mouse_cursor_monitor.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Global reference counter which is increased when a DesktopFrameWithCursor is
|
||||
// created and decreased when the same object is destructed. Only used for
|
||||
// debugging purposes to ensure that we never end up in state where
|
||||
// `g_ref_count` is larger than one since that could indicate a flickering
|
||||
// cursor (cursor-less version of the frame is not restored properly and it can
|
||||
// can lead to visible trails of old cursors).
|
||||
// See https://crbug.com/1421656#c99 for more details.
|
||||
int g_ref_count = 0;
|
||||
|
||||
uint64_t g_num_flicker_warnings = 0;
|
||||
|
||||
// Helper function that blends one image into another. Source image must be
|
||||
// pre-multiplied with the alpha channel. Destination is assumed to be opaque.
|
||||
void AlphaBlend(uint8_t* dest,
|
||||
int dest_stride,
|
||||
const uint8_t* src,
|
||||
int src_stride,
|
||||
const DesktopSize& size) {
|
||||
for (int y = 0; y < size.height(); ++y) {
|
||||
for (int x = 0; x < size.width(); ++x) {
|
||||
uint32_t base_alpha = 255 - src[x * DesktopFrame::kBytesPerPixel + 3];
|
||||
if (base_alpha == 255) {
|
||||
continue;
|
||||
} else if (base_alpha == 0) {
|
||||
memcpy(dest + x * DesktopFrame::kBytesPerPixel,
|
||||
src + x * DesktopFrame::kBytesPerPixel,
|
||||
DesktopFrame::kBytesPerPixel);
|
||||
} else {
|
||||
dest[x * DesktopFrame::kBytesPerPixel] =
|
||||
dest[x * DesktopFrame::kBytesPerPixel] * base_alpha / 255 +
|
||||
src[x * DesktopFrame::kBytesPerPixel];
|
||||
dest[x * DesktopFrame::kBytesPerPixel + 1] =
|
||||
dest[x * DesktopFrame::kBytesPerPixel + 1] * base_alpha / 255 +
|
||||
src[x * DesktopFrame::kBytesPerPixel + 1];
|
||||
dest[x * DesktopFrame::kBytesPerPixel + 2] =
|
||||
dest[x * DesktopFrame::kBytesPerPixel + 2] * base_alpha / 255 +
|
||||
src[x * DesktopFrame::kBytesPerPixel + 2];
|
||||
}
|
||||
}
|
||||
src += src_stride;
|
||||
dest += dest_stride;
|
||||
}
|
||||
}
|
||||
|
||||
// DesktopFrame wrapper that draws mouse on a frame and restores original
|
||||
// content before releasing the underlying frame.
|
||||
class DesktopFrameWithCursor : public DesktopFrame {
|
||||
public:
|
||||
// Takes ownership of `frame`.
|
||||
DesktopFrameWithCursor(std::unique_ptr<DesktopFrame> frame,
|
||||
const MouseCursor& cursor,
|
||||
const DesktopVector& position,
|
||||
const DesktopRect& previous_cursor_rect,
|
||||
bool cursor_changed);
|
||||
~DesktopFrameWithCursor() override;
|
||||
|
||||
DesktopFrameWithCursor(const DesktopFrameWithCursor&) = delete;
|
||||
DesktopFrameWithCursor& operator=(const DesktopFrameWithCursor&) = delete;
|
||||
|
||||
DesktopRect cursor_rect() const { return cursor_rect_; }
|
||||
|
||||
private:
|
||||
const std::unique_ptr<DesktopFrame> original_frame_;
|
||||
|
||||
DesktopVector restore_position_;
|
||||
std::unique_ptr<DesktopFrame> restore_frame_;
|
||||
DesktopRect cursor_rect_;
|
||||
};
|
||||
|
||||
DesktopFrameWithCursor::DesktopFrameWithCursor(
|
||||
std::unique_ptr<DesktopFrame> frame,
|
||||
const MouseCursor& cursor,
|
||||
const DesktopVector& position,
|
||||
const DesktopRect& previous_cursor_rect,
|
||||
bool cursor_changed)
|
||||
: DesktopFrame(frame->size(),
|
||||
frame->stride(),
|
||||
frame->data(),
|
||||
frame->shared_memory()),
|
||||
original_frame_(std::move(frame)) {
|
||||
++g_ref_count;
|
||||
MoveFrameInfoFrom(original_frame_.get());
|
||||
|
||||
DesktopVector image_pos = position.subtract(cursor.hotspot());
|
||||
cursor_rect_ = DesktopRect::MakeSize(cursor.image()->size());
|
||||
cursor_rect_.Translate(image_pos);
|
||||
DesktopVector cursor_origin = cursor_rect_.top_left();
|
||||
cursor_rect_.IntersectWith(DesktopRect::MakeSize(size()));
|
||||
|
||||
if (!previous_cursor_rect.equals(cursor_rect_)) {
|
||||
mutable_updated_region()->AddRect(cursor_rect_);
|
||||
// TODO(crbug:1323241) Update this code to properly handle the case where
|
||||
// |previous_cursor_rect| is outside of the boundaries of |frame|.
|
||||
// Any boundary check has to take into account the fact that
|
||||
// |previous_cursor_rect| can be in DPI or in pixels, based on the platform
|
||||
// we're running on.
|
||||
mutable_updated_region()->AddRect(previous_cursor_rect);
|
||||
} else if (cursor_changed) {
|
||||
mutable_updated_region()->AddRect(cursor_rect_);
|
||||
}
|
||||
|
||||
if (cursor_rect_.is_empty())
|
||||
return;
|
||||
|
||||
// Copy original screen content under cursor to `restore_frame_`.
|
||||
restore_position_ = cursor_rect_.top_left();
|
||||
restore_frame_.reset(new BasicDesktopFrame(cursor_rect_.size()));
|
||||
restore_frame_->CopyPixelsFrom(*this, cursor_rect_.top_left(),
|
||||
DesktopRect::MakeSize(restore_frame_->size()));
|
||||
|
||||
// Blit the cursor.
|
||||
uint8_t* cursor_rect_data =
|
||||
reinterpret_cast<uint8_t*>(data()) + cursor_rect_.top() * stride() +
|
||||
cursor_rect_.left() * DesktopFrame::kBytesPerPixel;
|
||||
DesktopVector origin_shift = cursor_rect_.top_left().subtract(cursor_origin);
|
||||
AlphaBlend(cursor_rect_data, stride(),
|
||||
cursor.image()->data() +
|
||||
origin_shift.y() * cursor.image()->stride() +
|
||||
origin_shift.x() * DesktopFrame::kBytesPerPixel,
|
||||
cursor.image()->stride(), cursor_rect_.size());
|
||||
}
|
||||
|
||||
DesktopFrameWithCursor::~DesktopFrameWithCursor() {
|
||||
if (--g_ref_count > 0) {
|
||||
++g_num_flicker_warnings;
|
||||
RTC_LOG(LS_WARNING) << "Cursor might be flickering; number of warnings="
|
||||
<< g_num_flicker_warnings;
|
||||
}
|
||||
// Restore original content of the frame.
|
||||
if (restore_frame_) {
|
||||
DesktopRect target_rect = DesktopRect::MakeSize(restore_frame_->size());
|
||||
target_rect.Translate(restore_position_);
|
||||
CopyPixelsFrom(restore_frame_->data(), restore_frame_->stride(),
|
||||
target_rect);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DesktopAndCursorComposer::DesktopAndCursorComposer(
|
||||
std::unique_ptr<DesktopCapturer> desktop_capturer,
|
||||
const DesktopCaptureOptions& options)
|
||||
: DesktopAndCursorComposer(desktop_capturer.release(),
|
||||
MouseCursorMonitor::Create(options).release()) {}
|
||||
|
||||
DesktopAndCursorComposer::DesktopAndCursorComposer(
|
||||
DesktopCapturer* desktop_capturer,
|
||||
MouseCursorMonitor* mouse_monitor)
|
||||
: desktop_capturer_(desktop_capturer), mouse_monitor_(mouse_monitor) {
|
||||
RTC_DCHECK(desktop_capturer_);
|
||||
}
|
||||
|
||||
DesktopAndCursorComposer::~DesktopAndCursorComposer() = default;
|
||||
|
||||
std::unique_ptr<DesktopAndCursorComposer>
|
||||
DesktopAndCursorComposer::CreateWithoutMouseCursorMonitor(
|
||||
std::unique_ptr<DesktopCapturer> desktop_capturer) {
|
||||
return std::unique_ptr<DesktopAndCursorComposer>(
|
||||
new DesktopAndCursorComposer(desktop_capturer.release(), nullptr));
|
||||
}
|
||||
|
||||
void DesktopAndCursorComposer::Start(DesktopCapturer::Callback* callback) {
|
||||
callback_ = callback;
|
||||
if (mouse_monitor_)
|
||||
mouse_monitor_->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
|
||||
desktop_capturer_->Start(this);
|
||||
}
|
||||
|
||||
void DesktopAndCursorComposer::SetMaxFrameRate(uint32_t max_frame_rate) {
|
||||
desktop_capturer_->SetMaxFrameRate(max_frame_rate);
|
||||
}
|
||||
|
||||
void DesktopAndCursorComposer::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
desktop_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory));
|
||||
}
|
||||
|
||||
void DesktopAndCursorComposer::CaptureFrame() {
|
||||
if (mouse_monitor_)
|
||||
mouse_monitor_->Capture();
|
||||
desktop_capturer_->CaptureFrame();
|
||||
}
|
||||
|
||||
void DesktopAndCursorComposer::SetExcludedWindow(WindowId window) {
|
||||
desktop_capturer_->SetExcludedWindow(window);
|
||||
}
|
||||
|
||||
bool DesktopAndCursorComposer::GetSourceList(SourceList* sources) {
|
||||
return desktop_capturer_->GetSourceList(sources);
|
||||
}
|
||||
|
||||
bool DesktopAndCursorComposer::SelectSource(SourceId id) {
|
||||
return desktop_capturer_->SelectSource(id);
|
||||
}
|
||||
|
||||
bool DesktopAndCursorComposer::FocusOnSelectedSource() {
|
||||
return desktop_capturer_->FocusOnSelectedSource();
|
||||
}
|
||||
|
||||
bool DesktopAndCursorComposer::IsOccluded(const DesktopVector& pos) {
|
||||
return desktop_capturer_->IsOccluded(pos);
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
DesktopCaptureMetadata DesktopAndCursorComposer::GetMetadata() {
|
||||
return desktop_capturer_->GetMetadata();
|
||||
}
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
|
||||
void DesktopAndCursorComposer::OnFrameCaptureStart() {
|
||||
callback_->OnFrameCaptureStart();
|
||||
}
|
||||
|
||||
void DesktopAndCursorComposer::OnCaptureResult(
|
||||
DesktopCapturer::Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) {
|
||||
if (frame && cursor_) {
|
||||
if (!frame->may_contain_cursor() &&
|
||||
frame->rect().Contains(cursor_position_) &&
|
||||
!desktop_capturer_->IsOccluded(cursor_position_)) {
|
||||
DesktopVector relative_position =
|
||||
cursor_position_.subtract(frame->top_left());
|
||||
#if defined(WEBRTC_MAC) || defined(CHROMEOS)
|
||||
// On OSX, the logical(DIP) and physical coordinates are used mixingly.
|
||||
// For example, the captured cursor has its size in physical pixels(2x)
|
||||
// and location in logical(DIP) pixels on Retina monitor. This will cause
|
||||
// problem when the desktop is mixed with Retina and non-Retina monitors.
|
||||
// So we use DIP pixel for all location info and compensate with the scale
|
||||
// factor of current frame to the `relative_position`.
|
||||
const float scale = frame->scale_factor();
|
||||
relative_position.set(relative_position.x() * scale,
|
||||
relative_position.y() * scale);
|
||||
#endif
|
||||
auto frame_with_cursor = std::make_unique<DesktopFrameWithCursor>(
|
||||
std::move(frame), *cursor_, relative_position, previous_cursor_rect_,
|
||||
cursor_changed_);
|
||||
previous_cursor_rect_ = frame_with_cursor->cursor_rect();
|
||||
cursor_changed_ = false;
|
||||
frame = std::move(frame_with_cursor);
|
||||
frame->set_may_contain_cursor(true);
|
||||
}
|
||||
}
|
||||
|
||||
callback_->OnCaptureResult(result, std::move(frame));
|
||||
}
|
||||
|
||||
void DesktopAndCursorComposer::OnMouseCursor(MouseCursor* cursor) {
|
||||
cursor_changed_ = true;
|
||||
cursor_.reset(cursor);
|
||||
}
|
||||
|
||||
void DesktopAndCursorComposer::OnMouseCursorPosition(
|
||||
const DesktopVector& position) {
|
||||
cursor_position_ = position;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
|
||||
|
||||
#include <memory>
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
#include "modules/desktop_capture/desktop_capture_metadata.h"
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/mouse_cursor.h"
|
||||
#include "modules/desktop_capture/mouse_cursor_monitor.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A wrapper for DesktopCapturer that also captures mouse using specified
|
||||
// MouseCursorMonitor and renders it on the generated streams.
|
||||
class RTC_EXPORT DesktopAndCursorComposer
|
||||
: public DesktopCapturer,
|
||||
public DesktopCapturer::Callback,
|
||||
public MouseCursorMonitor::Callback {
|
||||
public:
|
||||
// Creates a new composer that captures mouse cursor using
|
||||
// MouseCursorMonitor::Create(options) and renders it into the frames
|
||||
// generated by `desktop_capturer`.
|
||||
DesktopAndCursorComposer(std::unique_ptr<DesktopCapturer> desktop_capturer,
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
~DesktopAndCursorComposer() override;
|
||||
|
||||
DesktopAndCursorComposer(const DesktopAndCursorComposer&) = delete;
|
||||
DesktopAndCursorComposer& operator=(const DesktopAndCursorComposer&) = delete;
|
||||
|
||||
// Creates a new composer that relies on an external source for cursor shape
|
||||
// and position information via the MouseCursorMonitor::Callback interface.
|
||||
static std::unique_ptr<DesktopAndCursorComposer>
|
||||
CreateWithoutMouseCursorMonitor(
|
||||
std::unique_ptr<DesktopCapturer> desktop_capturer);
|
||||
|
||||
// DesktopCapturer interface.
|
||||
void Start(DesktopCapturer::Callback* callback) override;
|
||||
void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
||||
void CaptureFrame() override;
|
||||
void SetExcludedWindow(WindowId window) override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
bool FocusOnSelectedSource() override;
|
||||
bool IsOccluded(const DesktopVector& pos) override;
|
||||
void SetMaxFrameRate(uint32_t max_frame_rate) override;
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
DesktopCaptureMetadata GetMetadata() override;
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
|
||||
// MouseCursorMonitor::Callback interface.
|
||||
void OnMouseCursor(MouseCursor* cursor) override;
|
||||
void OnMouseCursorPosition(const DesktopVector& position) override;
|
||||
|
||||
private:
|
||||
// Allows test cases to use a fake MouseCursorMonitor implementation.
|
||||
friend class DesktopAndCursorComposerTest;
|
||||
|
||||
// Constructor to delegate both deprecated and new constructors and allows
|
||||
// test cases to use a fake MouseCursorMonitor implementation.
|
||||
DesktopAndCursorComposer(DesktopCapturer* desktop_capturer,
|
||||
MouseCursorMonitor* mouse_monitor);
|
||||
|
||||
// DesktopCapturer::Callback interface.
|
||||
void OnFrameCaptureStart() override;
|
||||
void OnCaptureResult(DesktopCapturer::Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) override;
|
||||
|
||||
const std::unique_ptr<DesktopCapturer> desktop_capturer_;
|
||||
const std::unique_ptr<MouseCursorMonitor> mouse_monitor_;
|
||||
|
||||
DesktopCapturer::Callback* callback_;
|
||||
|
||||
std::unique_ptr<MouseCursor> cursor_;
|
||||
DesktopVector cursor_position_;
|
||||
DesktopRect previous_cursor_rect_;
|
||||
bool cursor_changed_ = false;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_AND_CURSOR_COMPOSER_H_
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METADATA_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METADATA_H_
|
||||
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
#include "modules/portal/xdg_session_details.h"
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Container for the metadata associated with a desktop capturer.
|
||||
struct DesktopCaptureMetadata {
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
// Details about the XDG desktop session handle (used by wayland
|
||||
// implementation in remoting)
|
||||
xdg_portal::SessionDetails session_details;
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METADATA_H_
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_metrics_helper.h"
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
// This enum is logged via UMA so entries should not be reordered or have their
|
||||
// values changed. This should also be kept in sync with the values in the
|
||||
// DesktopCapturerId namespace.
|
||||
enum class SequentialDesktopCapturerId {
|
||||
kUnknown = 0,
|
||||
kWgcCapturerWin = 1,
|
||||
// kScreenCapturerWinMagnifier = 2,
|
||||
kWindowCapturerWinGdi = 3,
|
||||
kScreenCapturerWinGdi = 4,
|
||||
kScreenCapturerWinDirectx = 5,
|
||||
kMaxValue = kScreenCapturerWinDirectx
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void RecordCapturerImpl(uint32_t capturer_id) {
|
||||
SequentialDesktopCapturerId sequential_id;
|
||||
switch (capturer_id) {
|
||||
case DesktopCapturerId::kWgcCapturerWin:
|
||||
sequential_id = SequentialDesktopCapturerId::kWgcCapturerWin;
|
||||
break;
|
||||
case DesktopCapturerId::kWindowCapturerWinGdi:
|
||||
sequential_id = SequentialDesktopCapturerId::kWindowCapturerWinGdi;
|
||||
break;
|
||||
case DesktopCapturerId::kScreenCapturerWinGdi:
|
||||
sequential_id = SequentialDesktopCapturerId::kScreenCapturerWinGdi;
|
||||
break;
|
||||
case DesktopCapturerId::kScreenCapturerWinDirectx:
|
||||
sequential_id = SequentialDesktopCapturerId::kScreenCapturerWinDirectx;
|
||||
break;
|
||||
case DesktopCapturerId::kUnknown:
|
||||
default:
|
||||
sequential_id = SequentialDesktopCapturerId::kUnknown;
|
||||
}
|
||||
RTC_HISTOGRAM_ENUMERATION(
|
||||
"WebRTC.DesktopCapture.Win.DesktopCapturerImpl",
|
||||
static_cast<int>(sequential_id),
|
||||
static_cast<int>(SequentialDesktopCapturerId::kMaxValue));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METRICS_HELPER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METRICS_HELPER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
void RecordCapturerImpl(uint32_t capturer_id);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_METRICS_HELPER_H_
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
|
||||
#include "api/make_ref_counted.h"
|
||||
|
||||
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
|
||||
#include "modules/desktop_capture/mac/full_screen_mac_application_handler.h"
|
||||
#elif defined(WEBRTC_WIN)
|
||||
#include "modules/desktop_capture/win/full_screen_win_application_handler.h"
|
||||
#endif
|
||||
#if defined(WEBRTC_USE_PIPEWIRE)
|
||||
#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
|
||||
#endif
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DesktopCaptureOptions::DesktopCaptureOptions() {}
|
||||
DesktopCaptureOptions::DesktopCaptureOptions(
|
||||
const DesktopCaptureOptions& options) = default;
|
||||
DesktopCaptureOptions::DesktopCaptureOptions(DesktopCaptureOptions&& options) =
|
||||
default;
|
||||
DesktopCaptureOptions::~DesktopCaptureOptions() {}
|
||||
|
||||
DesktopCaptureOptions& DesktopCaptureOptions::operator=(
|
||||
const DesktopCaptureOptions& options) = default;
|
||||
DesktopCaptureOptions& DesktopCaptureOptions::operator=(
|
||||
DesktopCaptureOptions&& options) = default;
|
||||
|
||||
// static
|
||||
DesktopCaptureOptions DesktopCaptureOptions::CreateDefault() {
|
||||
DesktopCaptureOptions result;
|
||||
#if defined(WEBRTC_USE_X11)
|
||||
result.set_x_display(SharedXDisplay::CreateDefault());
|
||||
#endif
|
||||
#if defined(WEBRTC_USE_PIPEWIRE)
|
||||
result.set_screencast_stream(SharedScreenCastStream::CreateDefault());
|
||||
#endif
|
||||
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
|
||||
result.set_configuration_monitor(
|
||||
rtc::make_ref_counted<DesktopConfigurationMonitor>());
|
||||
result.set_full_screen_window_detector(
|
||||
rtc::make_ref_counted<FullScreenWindowDetector>(
|
||||
CreateFullScreenMacApplicationHandler));
|
||||
#elif defined(WEBRTC_WIN)
|
||||
result.set_full_screen_window_detector(
|
||||
rtc::make_ref_counted<FullScreenWindowDetector>(
|
||||
CreateFullScreenWinApplicationHandler));
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_
|
||||
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
#if defined(WEBRTC_USE_X11)
|
||||
#include "modules/desktop_capture/linux/x11/shared_x_display.h"
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_USE_PIPEWIRE)
|
||||
#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
|
||||
#include "modules/desktop_capture/mac/desktop_configuration_monitor.h"
|
||||
#endif
|
||||
|
||||
#include "modules/desktop_capture/full_screen_window_detector.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// An object that stores initialization parameters for screen and window
|
||||
// capturers.
|
||||
class RTC_EXPORT DesktopCaptureOptions {
|
||||
public:
|
||||
// Returns instance of DesktopCaptureOptions with default parameters. On Linux
|
||||
// also initializes X window connection. x_display() will be set to null if
|
||||
// X11 connection failed (e.g. DISPLAY isn't set).
|
||||
static DesktopCaptureOptions CreateDefault();
|
||||
|
||||
DesktopCaptureOptions();
|
||||
DesktopCaptureOptions(const DesktopCaptureOptions& options);
|
||||
DesktopCaptureOptions(DesktopCaptureOptions&& options);
|
||||
~DesktopCaptureOptions();
|
||||
|
||||
DesktopCaptureOptions& operator=(const DesktopCaptureOptions& options);
|
||||
DesktopCaptureOptions& operator=(DesktopCaptureOptions&& options);
|
||||
|
||||
#if defined(WEBRTC_USE_X11)
|
||||
const rtc::scoped_refptr<SharedXDisplay>& x_display() const {
|
||||
return x_display_;
|
||||
}
|
||||
void set_x_display(rtc::scoped_refptr<SharedXDisplay> x_display) {
|
||||
x_display_ = x_display;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
|
||||
// TODO(zijiehe): Remove both DesktopConfigurationMonitor and
|
||||
// FullScreenChromeWindowDetector out of DesktopCaptureOptions. It's not
|
||||
// reasonable for external consumers to set these two parameters.
|
||||
const rtc::scoped_refptr<DesktopConfigurationMonitor>& configuration_monitor()
|
||||
const {
|
||||
return configuration_monitor_;
|
||||
}
|
||||
// If nullptr is set, ScreenCapturer won't work and WindowCapturer may return
|
||||
// inaccurate result from IsOccluded() function.
|
||||
void set_configuration_monitor(
|
||||
rtc::scoped_refptr<DesktopConfigurationMonitor> m) {
|
||||
configuration_monitor_ = m;
|
||||
}
|
||||
|
||||
bool allow_iosurface() const { return allow_iosurface_; }
|
||||
void set_allow_iosurface(bool allow) { allow_iosurface_ = allow; }
|
||||
#endif
|
||||
|
||||
const rtc::scoped_refptr<FullScreenWindowDetector>&
|
||||
full_screen_window_detector() const {
|
||||
return full_screen_window_detector_;
|
||||
}
|
||||
void set_full_screen_window_detector(
|
||||
rtc::scoped_refptr<FullScreenWindowDetector> detector) {
|
||||
full_screen_window_detector_ = detector;
|
||||
}
|
||||
|
||||
// Flag indicating that the capturer should use screen change notifications.
|
||||
// Enables/disables use of XDAMAGE in the X11 capturer.
|
||||
bool use_update_notifications() const { return use_update_notifications_; }
|
||||
void set_use_update_notifications(bool use_update_notifications) {
|
||||
use_update_notifications_ = use_update_notifications;
|
||||
}
|
||||
|
||||
// Flag indicating if desktop effects (e.g. Aero) should be disabled when the
|
||||
// capturer is active. Currently used only on Windows.
|
||||
bool disable_effects() const { return disable_effects_; }
|
||||
void set_disable_effects(bool disable_effects) {
|
||||
disable_effects_ = disable_effects;
|
||||
}
|
||||
|
||||
// Flag that should be set if the consumer uses updated_region() and the
|
||||
// capturer should try to provide correct updated_region() for the frames it
|
||||
// generates (e.g. by comparing each frame with the previous one).
|
||||
bool detect_updated_region() const { return detect_updated_region_; }
|
||||
void set_detect_updated_region(bool detect_updated_region) {
|
||||
detect_updated_region_ = detect_updated_region;
|
||||
}
|
||||
|
||||
// Indicates that the capturer should try to include the cursor in the frame.
|
||||
// If it is able to do so it will set `DesktopFrame::may_contain_cursor()`.
|
||||
// Not all capturers will support including the cursor. If this value is false
|
||||
// or the cursor otherwise cannot be included in the frame, then cursor
|
||||
// metadata will be sent, though the capturer may choose to always send cursor
|
||||
// metadata.
|
||||
bool prefer_cursor_embedded() const { return prefer_cursor_embedded_; }
|
||||
void set_prefer_cursor_embedded(bool prefer_cursor_embedded) {
|
||||
prefer_cursor_embedded_ = prefer_cursor_embedded;
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_WIN)
|
||||
// Enumerating windows owned by the current process on Windows has some
|
||||
// complications due to |GetWindowText*()| APIs potentially causing a
|
||||
// deadlock (see the comments in the `GetWindowListHandler()` function in
|
||||
// window_capture_utils.cc for more details on the deadlock).
|
||||
// To avoid this issue, consumers can either ensure that the thread that runs
|
||||
// their message loop never waits on `GetSourceList()`, or they can set this
|
||||
// flag to false which will prevent windows running in the current process
|
||||
// from being enumerated and included in the results. Consumers can still
|
||||
// provide the WindowId for their own windows to `SelectSource()` and capture
|
||||
// them.
|
||||
bool enumerate_current_process_windows() const {
|
||||
return enumerate_current_process_windows_;
|
||||
}
|
||||
void set_enumerate_current_process_windows(
|
||||
bool enumerate_current_process_windows) {
|
||||
enumerate_current_process_windows_ = enumerate_current_process_windows;
|
||||
}
|
||||
|
||||
// Allowing directx based capturer or not, this capturer works on windows 7
|
||||
// with platform update / windows 8 or upper.
|
||||
bool allow_directx_capturer() const { return allow_directx_capturer_; }
|
||||
void set_allow_directx_capturer(bool enabled) {
|
||||
allow_directx_capturer_ = enabled;
|
||||
}
|
||||
|
||||
// Flag that may be set to allow use of the cropping window capturer (which
|
||||
// captures the screen & crops that to the window region in some cases). An
|
||||
// advantage of using this is significantly higher capture frame rates than
|
||||
// capturing the window directly. A disadvantage of using this is the
|
||||
// possibility of capturing unrelated content (e.g. overlapping windows that
|
||||
// aren't detected properly, or neighboring regions when moving/resizing the
|
||||
// captured window). Note: this flag influences the behavior of calls to
|
||||
// DesktopCapturer::CreateWindowCapturer; calls to
|
||||
// CroppingWindowCapturer::CreateCapturer ignore the flag (treat it as true).
|
||||
bool allow_cropping_window_capturer() const {
|
||||
return allow_cropping_window_capturer_;
|
||||
}
|
||||
void set_allow_cropping_window_capturer(bool allow) {
|
||||
allow_cropping_window_capturer_ = allow;
|
||||
}
|
||||
|
||||
#if defined(RTC_ENABLE_WIN_WGC)
|
||||
// This flag enables the WGC capturer for capturing the screen.
|
||||
// This capturer should offer similar or better performance than the cropping
|
||||
// capturer without the disadvantages listed above. However, the WGC capturer
|
||||
// is only available on Windows 10 version 1809 (Redstone 5) and up. This flag
|
||||
// will have no affect on older versions.
|
||||
// If set, and running a supported version of Win10, this flag will take
|
||||
// precedence over the cropping, directx, and magnification flags.
|
||||
bool allow_wgc_screen_capturer() const { return allow_wgc_screen_capturer_; }
|
||||
void set_allow_wgc_screen_capturer(bool allow) {
|
||||
allow_wgc_screen_capturer_ = allow;
|
||||
}
|
||||
|
||||
// This flag has the same effect as allow_wgc_screen_capturer but it only
|
||||
// enables or disables WGC for window capturing (not screen).
|
||||
bool allow_wgc_window_capturer() const { return allow_wgc_window_capturer_; }
|
||||
void set_allow_wgc_window_capturer(bool allow) {
|
||||
allow_wgc_window_capturer_ = allow;
|
||||
}
|
||||
|
||||
// This flag enables the WGC capturer for fallback capturer.
|
||||
// The flag is useful when the first capturer (eg. WindowCapturerWinGdi) is
|
||||
// unreliable in certain devices where WGC is supported, but not used by
|
||||
// default.
|
||||
bool allow_wgc_capturer_fallback() const {
|
||||
return allow_wgc_capturer_fallback_;
|
||||
}
|
||||
void set_allow_wgc_capturer_fallback(bool allow) {
|
||||
allow_wgc_capturer_fallback_ = allow;
|
||||
}
|
||||
|
||||
// This flag enables 0Hz mode in combination with the WGC capturer.
|
||||
// The flag has no effect if the allow_wgc_capturer flag is false.
|
||||
bool allow_wgc_zero_hertz() const { return allow_wgc_zero_hertz_; }
|
||||
void set_allow_wgc_zero_hertz(bool allow) { allow_wgc_zero_hertz_ = allow; }
|
||||
#endif // defined(RTC_ENABLE_WIN_WGC)
|
||||
#endif // defined(WEBRTC_WIN)
|
||||
|
||||
#if defined(WEBRTC_USE_PIPEWIRE)
|
||||
bool allow_pipewire() const { return allow_pipewire_; }
|
||||
void set_allow_pipewire(bool allow) { allow_pipewire_ = allow; }
|
||||
|
||||
const rtc::scoped_refptr<SharedScreenCastStream>& screencast_stream() const {
|
||||
return screencast_stream_;
|
||||
}
|
||||
void set_screencast_stream(
|
||||
rtc::scoped_refptr<SharedScreenCastStream> stream) {
|
||||
screencast_stream_ = stream;
|
||||
}
|
||||
|
||||
void set_width(uint32_t width) { width_ = width; }
|
||||
uint32_t get_width() const { return width_; }
|
||||
|
||||
void set_height(uint32_t height) { height_ = height; }
|
||||
uint32_t get_height() const { return height_; }
|
||||
|
||||
void set_pipewire_use_damage_region(bool use_damage_regions) {
|
||||
pipewire_use_damage_region_ = use_damage_regions;
|
||||
}
|
||||
bool pipewire_use_damage_region() const {
|
||||
return pipewire_use_damage_region_;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(WEBRTC_USE_X11)
|
||||
rtc::scoped_refptr<SharedXDisplay> x_display_;
|
||||
#endif
|
||||
#if defined(WEBRTC_USE_PIPEWIRE)
|
||||
// An instance of shared PipeWire ScreenCast stream we share between
|
||||
// BaseCapturerPipeWire and MouseCursorMonitorPipeWire as cursor information
|
||||
// is sent together with screen content.
|
||||
rtc::scoped_refptr<SharedScreenCastStream> screencast_stream_;
|
||||
#endif
|
||||
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
|
||||
rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_;
|
||||
bool allow_iosurface_ = false;
|
||||
#endif
|
||||
|
||||
rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_;
|
||||
|
||||
#if defined(WEBRTC_WIN)
|
||||
bool enumerate_current_process_windows_ = true;
|
||||
bool allow_directx_capturer_ = false;
|
||||
bool allow_cropping_window_capturer_ = false;
|
||||
#if defined(RTC_ENABLE_WIN_WGC)
|
||||
bool allow_wgc_screen_capturer_ = false;
|
||||
bool allow_wgc_window_capturer_ = false;
|
||||
bool allow_wgc_capturer_fallback_ = false;
|
||||
bool allow_wgc_zero_hertz_ = false;
|
||||
#endif
|
||||
#endif
|
||||
#if defined(WEBRTC_USE_X11)
|
||||
bool use_update_notifications_ = false;
|
||||
#else
|
||||
bool use_update_notifications_ = true;
|
||||
#endif
|
||||
bool disable_effects_ = true;
|
||||
bool detect_updated_region_ = false;
|
||||
bool prefer_cursor_embedded_ = false;
|
||||
#if defined(WEBRTC_USE_PIPEWIRE)
|
||||
bool allow_pipewire_ = false;
|
||||
bool pipewire_use_damage_region_ = true;
|
||||
uint32_t width_ = 0;
|
||||
uint32_t height_ = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
enum class CaptureType { kWindow, kScreen, kAnyScreenContent };
|
||||
|
||||
// Type used to identify windows on the desktop. Values are platform-specific:
|
||||
// - On Windows: HWND cast to intptr_t.
|
||||
// - On Linux (with X11): X11 Window (unsigned long) type cast to intptr_t.
|
||||
// - On OSX: integer window number.
|
||||
typedef intptr_t WindowId;
|
||||
|
||||
const WindowId kNullWindowId = 0;
|
||||
|
||||
const int64_t kInvalidDisplayId = -1;
|
||||
|
||||
// Type used to identify screens on the desktop. Values are platform-specific:
|
||||
// - On Windows: integer display device index.
|
||||
// - On OSX: CGDirectDisplayID cast to intptr_t.
|
||||
// - On Linux (with X11): TBD.
|
||||
// - On ChromeOS: display::Display::id() is an int64_t.
|
||||
// On Windows, ScreenId is implementation dependent: sending a ScreenId from one
|
||||
// implementation to another usually won't work correctly.
|
||||
#if defined(CHROMEOS)
|
||||
typedef int64_t ScreenId;
|
||||
#else
|
||||
typedef intptr_t ScreenId;
|
||||
#endif
|
||||
|
||||
// The screen id corresponds to all screen combined together.
|
||||
const ScreenId kFullDesktopScreenId = -1;
|
||||
|
||||
const ScreenId kInvalidScreenId = -2;
|
||||
|
||||
// Integers to attach to each DesktopFrame to differentiate the generator of
|
||||
// the frame. The entries in this namespace should remain in sync with the
|
||||
// SequentialDesktopCapturerId enum, which is logged via UMA.
|
||||
// `kScreenCapturerWinGdi` and `kScreenCapturerWinDirectx` values are preserved
|
||||
// to maintain compatibility
|
||||
namespace DesktopCapturerId {
|
||||
constexpr uint32_t CreateFourCC(char a, char b, char c, char d) {
|
||||
return ((static_cast<uint32_t>(a)) | (static_cast<uint32_t>(b) << 8) |
|
||||
(static_cast<uint32_t>(c) << 16) | (static_cast<uint32_t>(d) << 24));
|
||||
}
|
||||
|
||||
constexpr uint32_t kUnknown = 0;
|
||||
constexpr uint32_t kWgcCapturerWin = 1;
|
||||
constexpr uint32_t kScreenCapturerWinMagnifier = 2;
|
||||
constexpr uint32_t kWindowCapturerWinGdi = 3;
|
||||
constexpr uint32_t kScreenCapturerWinGdi = CreateFourCC('G', 'D', 'I', ' ');
|
||||
constexpr uint32_t kScreenCapturerWinDirectx = CreateFourCC('D', 'X', 'G', 'I');
|
||||
constexpr uint32_t kX11CapturerLinux = CreateFourCC('X', '1', '1', ' ');
|
||||
constexpr uint32_t kWaylandCapturerLinux = CreateFourCC('W', 'L', ' ', ' ');
|
||||
} // namespace DesktopCapturerId
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_TYPES_H_
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/cropping_window_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
#if defined(RTC_ENABLE_WIN_WGC)
|
||||
#include "modules/desktop_capture/win/wgc_capturer_win.h"
|
||||
#include "rtc_base/win/windows_version.h"
|
||||
#endif // defined(RTC_ENABLE_WIN_WGC)
|
||||
|
||||
#if defined(WEBRTC_USE_PIPEWIRE)
|
||||
#include "modules/desktop_capture/linux/wayland/base_capturer_pipewire.h"
|
||||
#endif
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
void LogDesktopCapturerFullscreenDetectorUsage() {
|
||||
RTC_HISTOGRAM_BOOLEAN("WebRTC.Screenshare.DesktopCapturerFullscreenDetector",
|
||||
true);
|
||||
}
|
||||
|
||||
DesktopCapturer::~DesktopCapturer() = default;
|
||||
|
||||
DelegatedSourceListController*
|
||||
DesktopCapturer::GetDelegatedSourceListController() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DesktopCapturer::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {}
|
||||
|
||||
void DesktopCapturer::SetExcludedWindow(WindowId window) {}
|
||||
|
||||
bool DesktopCapturer::GetSourceList(SourceList* sources) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DesktopCapturer::SelectSource(SourceId id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DesktopCapturer::FocusOnSelectedSource() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DesktopCapturer::IsOccluded(const DesktopVector& pos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateWindowCapturer(
|
||||
const DesktopCaptureOptions& options) {
|
||||
#if defined(RTC_ENABLE_WIN_WGC)
|
||||
if (options.allow_wgc_window_capturer() &&
|
||||
IsWgcSupported(CaptureType::kWindow)) {
|
||||
return WgcCapturerWin::CreateRawWindowCapturer(options);
|
||||
}
|
||||
#endif // defined(RTC_ENABLE_WIN_WGC)
|
||||
|
||||
#if defined(WEBRTC_WIN)
|
||||
if (options.allow_cropping_window_capturer()) {
|
||||
return CroppingWindowCapturer::CreateCapturer(options);
|
||||
}
|
||||
#endif // defined(WEBRTC_WIN)
|
||||
|
||||
std::unique_ptr<DesktopCapturer> capturer = CreateRawWindowCapturer(options);
|
||||
if (capturer && options.detect_updated_region()) {
|
||||
capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
|
||||
}
|
||||
|
||||
return capturer;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateScreenCapturer(
|
||||
const DesktopCaptureOptions& options) {
|
||||
#if defined(RTC_ENABLE_WIN_WGC)
|
||||
if (options.allow_wgc_screen_capturer() &&
|
||||
IsWgcSupported(CaptureType::kScreen)) {
|
||||
return WgcCapturerWin::CreateRawScreenCapturer(options);
|
||||
}
|
||||
#endif // defined(RTC_ENABLE_WIN_WGC)
|
||||
|
||||
std::unique_ptr<DesktopCapturer> capturer = CreateRawScreenCapturer(options);
|
||||
if (capturer && options.detect_updated_region()) {
|
||||
capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
|
||||
}
|
||||
|
||||
return capturer;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateGenericCapturer(
|
||||
const DesktopCaptureOptions& options) {
|
||||
std::unique_ptr<DesktopCapturer> capturer;
|
||||
|
||||
#if defined(WEBRTC_USE_PIPEWIRE)
|
||||
if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland()) {
|
||||
capturer = std::make_unique<BaseCapturerPipeWire>(
|
||||
options, CaptureType::kAnyScreenContent);
|
||||
}
|
||||
|
||||
if (capturer && options.detect_updated_region()) {
|
||||
capturer.reset(new DesktopCapturerDifferWrapper(std::move(capturer)));
|
||||
}
|
||||
#endif // defined(WEBRTC_USE_PIPEWIRE)
|
||||
|
||||
return capturer;
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11)
|
||||
bool DesktopCapturer::IsRunningUnderWayland() {
|
||||
const char* xdg_session_type = getenv("XDG_SESSION_TYPE");
|
||||
if (!xdg_session_type || strncmp(xdg_session_type, "wayland", 7) != 0)
|
||||
return false;
|
||||
|
||||
if (!(getenv("WAYLAND_DISPLAY")))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11)
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
// TODO(alcooper): Update include usage in downstream consumers and then change
|
||||
// this to a forward declaration.
|
||||
#include "modules/desktop_capture/delegated_source_list_controller.h"
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
#include "modules/desktop_capture/desktop_capture_metadata.h"
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
void RTC_EXPORT LogDesktopCapturerFullscreenDetectorUsage();
|
||||
|
||||
class DesktopCaptureOptions;
|
||||
class DesktopFrame;
|
||||
|
||||
// Abstract interface for screen and window capturers.
|
||||
class RTC_EXPORT DesktopCapturer {
|
||||
public:
|
||||
enum class Result {
|
||||
// The frame was captured successfully.
|
||||
SUCCESS,
|
||||
|
||||
// There was a temporary error. The caller should continue calling
|
||||
// CaptureFrame(), in the expectation that it will eventually recover.
|
||||
ERROR_TEMPORARY,
|
||||
|
||||
// Capture has failed and will keep failing if the caller tries calling
|
||||
// CaptureFrame() again.
|
||||
ERROR_PERMANENT,
|
||||
|
||||
MAX_VALUE = ERROR_PERMANENT
|
||||
};
|
||||
|
||||
// Interface that must be implemented by the DesktopCapturer consumers.
|
||||
class Callback {
|
||||
public:
|
||||
// Called before a frame capture is started.
|
||||
virtual void OnFrameCaptureStart() {}
|
||||
|
||||
// Called after a frame has been captured. `frame` is not nullptr if and
|
||||
// only if `result` is SUCCESS.
|
||||
virtual void OnCaptureResult(Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Callback() {}
|
||||
};
|
||||
|
||||
#if defined(CHROMEOS)
|
||||
typedef int64_t SourceId;
|
||||
#else
|
||||
typedef intptr_t SourceId;
|
||||
#endif
|
||||
|
||||
static_assert(std::is_same<SourceId, ScreenId>::value,
|
||||
"SourceId should be a same type as ScreenId.");
|
||||
|
||||
struct Source {
|
||||
// The unique id to represent a Source of current DesktopCapturer.
|
||||
SourceId id;
|
||||
|
||||
// Title of the window or screen in UTF-8 encoding, maybe empty. This field
|
||||
// should not be used to identify a source.
|
||||
std::string title;
|
||||
|
||||
#if defined(CHROMEOS)
|
||||
// TODO(https://crbug.com/1369162): Remove or refactor this value.
|
||||
WindowId in_process_id = kNullWindowId;
|
||||
#endif
|
||||
|
||||
// The display's unique ID. If no ID is defined, it will hold the value
|
||||
// kInvalidDisplayId.
|
||||
int64_t display_id = kInvalidDisplayId;
|
||||
};
|
||||
|
||||
typedef std::vector<Source> SourceList;
|
||||
|
||||
virtual ~DesktopCapturer();
|
||||
|
||||
// Called at the beginning of a capturing session. `callback` must remain
|
||||
// valid until capturer is destroyed.
|
||||
virtual void Start(Callback* callback) = 0;
|
||||
|
||||
// Sets max frame rate for the capturer. This is best effort and may not be
|
||||
// supported by all capturers. This will only affect the frequency at which
|
||||
// new frames are available, not the frequency at which you are allowed to
|
||||
// capture the frames.
|
||||
virtual void SetMaxFrameRate(uint32_t max_frame_rate) {}
|
||||
|
||||
// Returns a valid pointer if the capturer requires the user to make a
|
||||
// selection from a source list provided by the capturer.
|
||||
// Returns nullptr if the capturer does not provide a UI for the user to make
|
||||
// a selection.
|
||||
//
|
||||
// Callers should not take ownership of the returned pointer, but it is
|
||||
// guaranteed to be valid as long as the desktop_capturer is valid.
|
||||
// Note that consumers should still use GetSourceList and SelectSource, but
|
||||
// their behavior may be modified if this returns a value. See those methods
|
||||
// for a more in-depth discussion of those potential modifications.
|
||||
virtual DelegatedSourceListController* GetDelegatedSourceListController();
|
||||
|
||||
// Sets SharedMemoryFactory that will be used to create buffers for the
|
||||
// captured frames. The factory can be invoked on a thread other than the one
|
||||
// where CaptureFrame() is called. It will be destroyed on the same thread.
|
||||
// Shared memory is currently supported only by some DesktopCapturer
|
||||
// implementations.
|
||||
virtual void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory);
|
||||
|
||||
// Captures next frame, and involve callback provided by Start() function.
|
||||
// Pending capture requests are canceled when DesktopCapturer is deleted.
|
||||
virtual void CaptureFrame() = 0;
|
||||
|
||||
// Sets the window to be excluded from the captured image in the future
|
||||
// Capture calls. Used to exclude the screenshare notification window for
|
||||
// screen capturing.
|
||||
virtual void SetExcludedWindow(WindowId window);
|
||||
|
||||
// TODO(zijiehe): Following functions should be pure virtual. The default
|
||||
// implementations are for backward compatibility only. Remove default
|
||||
// implementations once all DesktopCapturer implementations in Chromium have
|
||||
// implemented these functions.
|
||||
|
||||
// Gets a list of sources current capturer supports. Returns false in case of
|
||||
// a failure.
|
||||
// For DesktopCapturer implementations to capture screens, this function
|
||||
// should return monitors.
|
||||
// For DesktopCapturer implementations to capture windows, this function
|
||||
// should only return root windows owned by applications.
|
||||
//
|
||||
// Note that capturers who use a delegated source list will return a
|
||||
// SourceList with exactly one value, but it may not be viable for capture
|
||||
// (e.g. CaptureFrame will return ERROR_TEMPORARY) until a selection has been
|
||||
// made.
|
||||
virtual bool GetSourceList(SourceList* sources);
|
||||
|
||||
// Selects a source to be captured. Returns false in case of a failure (e.g.
|
||||
// if there is no source with the specified type and id.)
|
||||
//
|
||||
// Note that some capturers with delegated source lists may also support
|
||||
// selecting a SourceID that is not in the returned source list as a form of
|
||||
// restore token.
|
||||
virtual bool SelectSource(SourceId id);
|
||||
|
||||
// Brings the selected source to the front and sets the input focus on it.
|
||||
// Returns false in case of a failure or no source has been selected or the
|
||||
// implementation does not support this functionality.
|
||||
virtual bool FocusOnSelectedSource();
|
||||
|
||||
// Returns true if the `pos` on the selected source is covered by other
|
||||
// elements on the display, and is not visible to the users.
|
||||
// `pos` is in full desktop coordinates, i.e. the top-left monitor always
|
||||
// starts from (0, 0).
|
||||
// The return value if `pos` is out of the scope of the source is undefined.
|
||||
virtual bool IsOccluded(const DesktopVector& pos);
|
||||
|
||||
// Creates a DesktopCapturer instance which targets to capture windows.
|
||||
static std::unique_ptr<DesktopCapturer> CreateWindowCapturer(
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
// Creates a DesktopCapturer instance which targets to capture screens.
|
||||
static std::unique_ptr<DesktopCapturer> CreateScreenCapturer(
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
// Creates a DesktopCapturer instance which targets to capture windows and
|
||||
// screens.
|
||||
static std::unique_ptr<DesktopCapturer> CreateGenericCapturer(
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
#if defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11)
|
||||
static bool IsRunningUnderWayland();
|
||||
|
||||
virtual void UpdateResolution(uint32_t width, uint32_t height) {}
|
||||
#endif // defined(WEBRTC_USE_PIPEWIRE) || defined(WEBRTC_USE_X11)
|
||||
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
// Populates implementation specific metadata into the passed in pointer.
|
||||
// Classes can choose to override it or use the default no-op implementation.
|
||||
virtual DesktopCaptureMetadata GetMetadata() { return {}; }
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
|
||||
protected:
|
||||
// CroppingWindowCapturer needs to create raw capturers without wrappers, so
|
||||
// the following two functions are protected.
|
||||
|
||||
// Creates a platform specific DesktopCapturer instance which targets to
|
||||
// capture windows.
|
||||
static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer(
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
// Creates a platform specific DesktopCapturer instance which targets to
|
||||
// capture screens.
|
||||
static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer(
|
||||
const DesktopCaptureOptions& options);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_H_
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer_differ_wrapper.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/differ_block.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns true if (0, 0) - (`width`, `height`) vector in `old_buffer` and
|
||||
// `new_buffer` are equal. `width` should be less than 32
|
||||
// (defined by kBlockSize), otherwise BlockDifference() should be used.
|
||||
bool PartialBlockDifference(const uint8_t* old_buffer,
|
||||
const uint8_t* new_buffer,
|
||||
int width,
|
||||
int height,
|
||||
int stride) {
|
||||
RTC_DCHECK_LT(width, kBlockSize);
|
||||
const int width_bytes = width * DesktopFrame::kBytesPerPixel;
|
||||
for (int i = 0; i < height; i++) {
|
||||
if (memcmp(old_buffer, new_buffer, width_bytes) != 0) {
|
||||
return true;
|
||||
}
|
||||
old_buffer += stride;
|
||||
new_buffer += stride;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compares columns in the range of [`left`, `right`), in a row in the
|
||||
// range of [`top`, `top` + `height`), starts from `old_buffer` and
|
||||
// `new_buffer`, and outputs updated regions into `output`. `stride` is the
|
||||
// DesktopFrame::stride().
|
||||
void CompareRow(const uint8_t* old_buffer,
|
||||
const uint8_t* new_buffer,
|
||||
const int left,
|
||||
const int right,
|
||||
const int top,
|
||||
const int bottom,
|
||||
const int stride,
|
||||
DesktopRegion* const output) {
|
||||
const int block_x_offset = kBlockSize * DesktopFrame::kBytesPerPixel;
|
||||
const int width = right - left;
|
||||
const int height = bottom - top;
|
||||
const int block_count = (width - 1) / kBlockSize;
|
||||
const int last_block_width = width - block_count * kBlockSize;
|
||||
RTC_DCHECK_GT(last_block_width, 0);
|
||||
RTC_DCHECK_LE(last_block_width, kBlockSize);
|
||||
|
||||
// The first block-column in a continuous dirty area in current block-row.
|
||||
int first_dirty_x_block = -1;
|
||||
|
||||
// We always need to add dirty area into `output` in the last block, so handle
|
||||
// it separatedly.
|
||||
for (int x = 0; x < block_count; x++) {
|
||||
if (BlockDifference(old_buffer, new_buffer, height, stride)) {
|
||||
if (first_dirty_x_block == -1) {
|
||||
// This is the first dirty block in a continuous dirty area.
|
||||
first_dirty_x_block = x;
|
||||
}
|
||||
} else if (first_dirty_x_block != -1) {
|
||||
// The block on the left is the last dirty block in a continuous
|
||||
// dirty area.
|
||||
output->AddRect(
|
||||
DesktopRect::MakeLTRB(first_dirty_x_block * kBlockSize + left, top,
|
||||
x * kBlockSize + left, bottom));
|
||||
first_dirty_x_block = -1;
|
||||
}
|
||||
old_buffer += block_x_offset;
|
||||
new_buffer += block_x_offset;
|
||||
}
|
||||
|
||||
bool last_block_diff;
|
||||
if (last_block_width < kBlockSize) {
|
||||
// The last one is a partial vector.
|
||||
last_block_diff = PartialBlockDifference(old_buffer, new_buffer,
|
||||
last_block_width, height, stride);
|
||||
} else {
|
||||
last_block_diff = BlockDifference(old_buffer, new_buffer, height, stride);
|
||||
}
|
||||
if (last_block_diff) {
|
||||
if (first_dirty_x_block == -1) {
|
||||
first_dirty_x_block = block_count;
|
||||
}
|
||||
output->AddRect(DesktopRect::MakeLTRB(
|
||||
first_dirty_x_block * kBlockSize + left, top, right, bottom));
|
||||
} else if (first_dirty_x_block != -1) {
|
||||
output->AddRect(
|
||||
DesktopRect::MakeLTRB(first_dirty_x_block * kBlockSize + left, top,
|
||||
block_count * kBlockSize + left, bottom));
|
||||
}
|
||||
}
|
||||
|
||||
// Compares `rect` area in `old_frame` and `new_frame`, and outputs dirty
|
||||
// regions into `output`.
|
||||
void CompareFrames(const DesktopFrame& old_frame,
|
||||
const DesktopFrame& new_frame,
|
||||
DesktopRect rect,
|
||||
DesktopRegion* const output) {
|
||||
RTC_DCHECK(old_frame.size().equals(new_frame.size()));
|
||||
RTC_DCHECK_EQ(old_frame.stride(), new_frame.stride());
|
||||
rect.IntersectWith(DesktopRect::MakeSize(old_frame.size()));
|
||||
|
||||
const int y_block_count = (rect.height() - 1) / kBlockSize;
|
||||
const int last_y_block_height = rect.height() - y_block_count * kBlockSize;
|
||||
// Offset from the start of one block-row to the next.
|
||||
const int block_y_stride = old_frame.stride() * kBlockSize;
|
||||
const uint8_t* prev_block_row_start =
|
||||
old_frame.GetFrameDataAtPos(rect.top_left());
|
||||
const uint8_t* curr_block_row_start =
|
||||
new_frame.GetFrameDataAtPos(rect.top_left());
|
||||
|
||||
int top = rect.top();
|
||||
// The last row may have a different height, so we handle it separately.
|
||||
for (int y = 0; y < y_block_count; y++) {
|
||||
CompareRow(prev_block_row_start, curr_block_row_start, rect.left(),
|
||||
rect.right(), top, top + kBlockSize, old_frame.stride(), output);
|
||||
top += kBlockSize;
|
||||
prev_block_row_start += block_y_stride;
|
||||
curr_block_row_start += block_y_stride;
|
||||
}
|
||||
CompareRow(prev_block_row_start, curr_block_row_start, rect.left(),
|
||||
rect.right(), top, top + last_y_block_height, old_frame.stride(),
|
||||
output);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DesktopCapturerDifferWrapper::DesktopCapturerDifferWrapper(
|
||||
std::unique_ptr<DesktopCapturer> base_capturer)
|
||||
: base_capturer_(std::move(base_capturer)) {
|
||||
RTC_DCHECK(base_capturer_);
|
||||
}
|
||||
|
||||
DesktopCapturerDifferWrapper::~DesktopCapturerDifferWrapper() {}
|
||||
|
||||
void DesktopCapturerDifferWrapper::Start(DesktopCapturer::Callback* callback) {
|
||||
callback_ = callback;
|
||||
base_capturer_->Start(this);
|
||||
}
|
||||
|
||||
void DesktopCapturerDifferWrapper::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
base_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory));
|
||||
}
|
||||
|
||||
void DesktopCapturerDifferWrapper::CaptureFrame() {
|
||||
base_capturer_->CaptureFrame();
|
||||
}
|
||||
|
||||
void DesktopCapturerDifferWrapper::SetExcludedWindow(WindowId window) {
|
||||
base_capturer_->SetExcludedWindow(window);
|
||||
}
|
||||
|
||||
bool DesktopCapturerDifferWrapper::GetSourceList(SourceList* sources) {
|
||||
return base_capturer_->GetSourceList(sources);
|
||||
}
|
||||
|
||||
bool DesktopCapturerDifferWrapper::SelectSource(SourceId id) {
|
||||
return base_capturer_->SelectSource(id);
|
||||
}
|
||||
|
||||
bool DesktopCapturerDifferWrapper::FocusOnSelectedSource() {
|
||||
return base_capturer_->FocusOnSelectedSource();
|
||||
}
|
||||
|
||||
bool DesktopCapturerDifferWrapper::IsOccluded(const DesktopVector& pos) {
|
||||
return base_capturer_->IsOccluded(pos);
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
DesktopCaptureMetadata DesktopCapturerDifferWrapper::GetMetadata() {
|
||||
return base_capturer_->GetMetadata();
|
||||
}
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
|
||||
void DesktopCapturerDifferWrapper::OnCaptureResult(
|
||||
Result result,
|
||||
std::unique_ptr<DesktopFrame> input_frame) {
|
||||
int64_t start_time_nanos = rtc::TimeNanos();
|
||||
if (!input_frame) {
|
||||
callback_->OnCaptureResult(result, nullptr);
|
||||
return;
|
||||
}
|
||||
RTC_DCHECK(result == Result::SUCCESS);
|
||||
|
||||
std::unique_ptr<SharedDesktopFrame> frame =
|
||||
SharedDesktopFrame::Wrap(std::move(input_frame));
|
||||
if (last_frame_ && (last_frame_->size().width() != frame->size().width() ||
|
||||
last_frame_->size().height() != frame->size().height() ||
|
||||
last_frame_->stride() != frame->stride())) {
|
||||
last_frame_.reset();
|
||||
}
|
||||
|
||||
if (last_frame_) {
|
||||
DesktopRegion hints;
|
||||
hints.Swap(frame->mutable_updated_region());
|
||||
for (DesktopRegion::Iterator it(hints); !it.IsAtEnd(); it.Advance()) {
|
||||
CompareFrames(*last_frame_, *frame, it.rect(),
|
||||
frame->mutable_updated_region());
|
||||
}
|
||||
} else {
|
||||
frame->mutable_updated_region()->SetRect(
|
||||
DesktopRect::MakeSize(frame->size()));
|
||||
}
|
||||
last_frame_ = frame->Share();
|
||||
|
||||
frame->set_capture_time_ms(frame->capture_time_ms() +
|
||||
(rtc::TimeNanos() - start_time_nanos) /
|
||||
rtc::kNumNanosecsPerMillisec);
|
||||
callback_->OnCaptureResult(result, std::move(frame));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_
|
||||
|
||||
#include <memory>
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
#include "modules/desktop_capture/desktop_capture_metadata.h"
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/shared_desktop_frame.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// DesktopCapturer wrapper that calculates updated_region() by comparing frames
|
||||
// content. This class always expects the underlying DesktopCapturer
|
||||
// implementation returns a superset of updated regions in DestkopFrame. If a
|
||||
// DesktopCapturer implementation does not know the updated region, it should
|
||||
// set updated_region() to full frame.
|
||||
//
|
||||
// This class marks entire frame as updated if the frame size or frame stride
|
||||
// has been changed.
|
||||
class RTC_EXPORT DesktopCapturerDifferWrapper
|
||||
: public DesktopCapturer,
|
||||
public DesktopCapturer::Callback {
|
||||
public:
|
||||
// Creates a DesktopCapturerDifferWrapper with a DesktopCapturer
|
||||
// implementation, and takes its ownership.
|
||||
explicit DesktopCapturerDifferWrapper(
|
||||
std::unique_ptr<DesktopCapturer> base_capturer);
|
||||
|
||||
~DesktopCapturerDifferWrapper() override;
|
||||
|
||||
// DesktopCapturer interface.
|
||||
void Start(DesktopCapturer::Callback* callback) override;
|
||||
void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
||||
void CaptureFrame() override;
|
||||
void SetExcludedWindow(WindowId window) override;
|
||||
bool GetSourceList(SourceList* screens) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
bool FocusOnSelectedSource() override;
|
||||
bool IsOccluded(const DesktopVector& pos) override;
|
||||
#if defined(WEBRTC_USE_GIO)
|
||||
DesktopCaptureMetadata GetMetadata() override;
|
||||
#endif // defined(WEBRTC_USE_GIO)
|
||||
private:
|
||||
// DesktopCapturer::Callback interface.
|
||||
void OnCaptureResult(Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) override;
|
||||
|
||||
const std::unique_ptr<DesktopCapturer> base_capturer_;
|
||||
DesktopCapturer::Callback* callback_;
|
||||
std::unique_ptr<SharedDesktopFrame> last_frame_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_DIFFER_WRAPPER_H_
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer_wrapper.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DesktopCapturerWrapper::DesktopCapturerWrapper(
|
||||
std::unique_ptr<DesktopCapturer> base_capturer)
|
||||
: base_capturer_(std::move(base_capturer)) {
|
||||
RTC_DCHECK(base_capturer_);
|
||||
}
|
||||
|
||||
DesktopCapturerWrapper::~DesktopCapturerWrapper() = default;
|
||||
|
||||
void DesktopCapturerWrapper::Start(Callback* callback) {
|
||||
base_capturer_->Start(callback);
|
||||
}
|
||||
|
||||
void DesktopCapturerWrapper::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
base_capturer_->SetSharedMemoryFactory(std::move(shared_memory_factory));
|
||||
}
|
||||
|
||||
void DesktopCapturerWrapper::CaptureFrame() {
|
||||
base_capturer_->CaptureFrame();
|
||||
}
|
||||
|
||||
void DesktopCapturerWrapper::SetExcludedWindow(WindowId window) {
|
||||
base_capturer_->SetExcludedWindow(window);
|
||||
}
|
||||
|
||||
bool DesktopCapturerWrapper::GetSourceList(SourceList* sources) {
|
||||
return base_capturer_->GetSourceList(sources);
|
||||
}
|
||||
|
||||
bool DesktopCapturerWrapper::SelectSource(SourceId id) {
|
||||
return base_capturer_->SelectSource(id);
|
||||
}
|
||||
|
||||
bool DesktopCapturerWrapper::FocusOnSelectedSource() {
|
||||
return base_capturer_->FocusOnSelectedSource();
|
||||
}
|
||||
|
||||
bool DesktopCapturerWrapper::IsOccluded(const DesktopVector& pos) {
|
||||
return base_capturer_->IsOccluded(pos);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Wraps a DesktopCapturer and forwards all the function calls to it.
|
||||
class DesktopCapturerWrapper : public DesktopCapturer {
|
||||
public:
|
||||
explicit DesktopCapturerWrapper(
|
||||
std::unique_ptr<DesktopCapturer> base_capturer);
|
||||
~DesktopCapturerWrapper() override;
|
||||
|
||||
// DesktopCapturer implementations.
|
||||
void Start(Callback* callback) override;
|
||||
void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
||||
void CaptureFrame() override;
|
||||
void SetExcludedWindow(WindowId window) override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
bool FocusOnSelectedSource() override;
|
||||
bool IsOccluded(const DesktopVector& pos) override;
|
||||
|
||||
protected:
|
||||
// Guaranteed to be valid.
|
||||
const std::unique_ptr<DesktopCapturer> base_capturer_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "third_party/libyuv/include/libyuv/planar_functions.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DesktopFrame::DesktopFrame(DesktopSize size,
|
||||
int stride,
|
||||
uint8_t* data,
|
||||
SharedMemory* shared_memory)
|
||||
: data_(data),
|
||||
shared_memory_(shared_memory),
|
||||
size_(size),
|
||||
stride_(stride),
|
||||
capture_time_ms_(0),
|
||||
capturer_id_(DesktopCapturerId::kUnknown) {
|
||||
RTC_DCHECK(size_.width() >= 0);
|
||||
RTC_DCHECK(size_.height() >= 0);
|
||||
}
|
||||
|
||||
DesktopFrame::~DesktopFrame() = default;
|
||||
|
||||
void DesktopFrame::CopyPixelsFrom(const uint8_t* src_buffer,
|
||||
int src_stride,
|
||||
const DesktopRect& dest_rect) {
|
||||
RTC_CHECK(DesktopRect::MakeSize(size()).ContainsRect(dest_rect));
|
||||
|
||||
uint8_t* dest = GetFrameDataAtPos(dest_rect.top_left());
|
||||
libyuv::CopyPlane(src_buffer, src_stride, dest, stride(),
|
||||
DesktopFrame::kBytesPerPixel * dest_rect.width(),
|
||||
dest_rect.height());
|
||||
}
|
||||
|
||||
void DesktopFrame::CopyPixelsFrom(const DesktopFrame& src_frame,
|
||||
const DesktopVector& src_pos,
|
||||
const DesktopRect& dest_rect) {
|
||||
RTC_CHECK(DesktopRect::MakeSize(src_frame.size())
|
||||
.ContainsRect(
|
||||
DesktopRect::MakeOriginSize(src_pos, dest_rect.size())));
|
||||
|
||||
CopyPixelsFrom(src_frame.GetFrameDataAtPos(src_pos), src_frame.stride(),
|
||||
dest_rect);
|
||||
}
|
||||
|
||||
bool DesktopFrame::CopyIntersectingPixelsFrom(const DesktopFrame& src_frame,
|
||||
double horizontal_scale,
|
||||
double vertical_scale) {
|
||||
const DesktopVector& origin = top_left();
|
||||
const DesktopVector& src_frame_origin = src_frame.top_left();
|
||||
|
||||
DesktopVector src_frame_offset = src_frame_origin.subtract(origin);
|
||||
|
||||
// Determine the intersection, first adjusting its origin to account for any
|
||||
// DPI scaling.
|
||||
DesktopRect intersection_rect = src_frame.rect();
|
||||
if (horizontal_scale != 1.0 || vertical_scale != 1.0) {
|
||||
DesktopVector origin_adjustment(
|
||||
static_cast<int>(
|
||||
std::round((horizontal_scale - 1.0) * src_frame_offset.x())),
|
||||
static_cast<int>(
|
||||
std::round((vertical_scale - 1.0) * src_frame_offset.y())));
|
||||
|
||||
intersection_rect.Translate(origin_adjustment);
|
||||
|
||||
src_frame_offset = src_frame_offset.add(origin_adjustment);
|
||||
}
|
||||
|
||||
intersection_rect.IntersectWith(rect());
|
||||
if (intersection_rect.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Translate the intersection rect to be relative to the outer rect.
|
||||
intersection_rect.Translate(-origin.x(), -origin.y());
|
||||
|
||||
// Determine source position for the copy (offsets of outer frame from
|
||||
// source origin, if positive).
|
||||
int32_t src_pos_x = std::max(0, -src_frame_offset.x());
|
||||
int32_t src_pos_y = std::max(0, -src_frame_offset.y());
|
||||
|
||||
CopyPixelsFrom(src_frame, DesktopVector(src_pos_x, src_pos_y),
|
||||
intersection_rect);
|
||||
return true;
|
||||
}
|
||||
|
||||
DesktopRect DesktopFrame::rect() const {
|
||||
const float scale = scale_factor();
|
||||
// Only scale the size.
|
||||
return DesktopRect::MakeXYWH(top_left().x(), top_left().y(),
|
||||
size().width() / scale, size().height() / scale);
|
||||
}
|
||||
|
||||
float DesktopFrame::scale_factor() const {
|
||||
float scale = 1.0f;
|
||||
|
||||
#if defined(WEBRTC_MAC) || defined(CHROMEOS)
|
||||
// At least on Windows the logical and physical pixel are the same
|
||||
// See http://crbug.com/948362.
|
||||
if (!dpi().is_zero() && dpi().x() == dpi().y())
|
||||
scale = dpi().x() / kStandardDPI;
|
||||
#endif
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
uint8_t* DesktopFrame::GetFrameDataAtPos(const DesktopVector& pos) const {
|
||||
return data() + stride() * pos.y() + DesktopFrame::kBytesPerPixel * pos.x();
|
||||
}
|
||||
|
||||
void DesktopFrame::CopyFrameInfoFrom(const DesktopFrame& other) {
|
||||
set_dpi(other.dpi());
|
||||
set_capture_time_ms(other.capture_time_ms());
|
||||
set_capturer_id(other.capturer_id());
|
||||
*mutable_updated_region() = other.updated_region();
|
||||
set_top_left(other.top_left());
|
||||
set_icc_profile(other.icc_profile());
|
||||
set_may_contain_cursor(other.may_contain_cursor());
|
||||
}
|
||||
|
||||
void DesktopFrame::MoveFrameInfoFrom(DesktopFrame* other) {
|
||||
set_dpi(other->dpi());
|
||||
set_capture_time_ms(other->capture_time_ms());
|
||||
set_capturer_id(other->capturer_id());
|
||||
mutable_updated_region()->Swap(other->mutable_updated_region());
|
||||
set_top_left(other->top_left());
|
||||
set_icc_profile(other->icc_profile());
|
||||
set_may_contain_cursor(other->may_contain_cursor());
|
||||
}
|
||||
|
||||
bool DesktopFrame::FrameDataIsBlack() const {
|
||||
if (size().is_empty())
|
||||
return false;
|
||||
|
||||
uint32_t* pixel = reinterpret_cast<uint32_t*>(data());
|
||||
for (int i = 0; i < size().width() * size().height(); ++i) {
|
||||
if (*pixel++)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DesktopFrame::SetFrameDataToBlack() {
|
||||
const uint8_t kBlackPixelValue = 0x00;
|
||||
memset(data(), kBlackPixelValue, stride() * size().height());
|
||||
}
|
||||
|
||||
BasicDesktopFrame::BasicDesktopFrame(DesktopSize size)
|
||||
: DesktopFrame(size,
|
||||
kBytesPerPixel * size.width(),
|
||||
new uint8_t[kBytesPerPixel * size.width() * size.height()](),
|
||||
nullptr) {}
|
||||
|
||||
BasicDesktopFrame::~BasicDesktopFrame() {
|
||||
delete[] data_;
|
||||
}
|
||||
|
||||
// static
|
||||
DesktopFrame* BasicDesktopFrame::CopyOf(const DesktopFrame& frame) {
|
||||
DesktopFrame* result = new BasicDesktopFrame(frame.size());
|
||||
// TODO(crbug.com/1330019): Temporary workaround for a known libyuv crash when
|
||||
// the height or width is 0. Remove this once this change has been merged.
|
||||
if (frame.size().width() && frame.size().height()) {
|
||||
libyuv::CopyPlane(frame.data(), frame.stride(), result->data(),
|
||||
result->stride(), frame.size().width() * kBytesPerPixel,
|
||||
frame.size().height());
|
||||
}
|
||||
result->CopyFrameInfoFrom(frame);
|
||||
return result;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopFrame> SharedMemoryDesktopFrame::Create(
|
||||
DesktopSize size,
|
||||
SharedMemoryFactory* shared_memory_factory) {
|
||||
RTC_DCHECK(shared_memory_factory);
|
||||
|
||||
size_t buffer_size = size.height() * size.width() * kBytesPerPixel;
|
||||
std::unique_ptr<SharedMemory> shared_memory =
|
||||
shared_memory_factory->CreateSharedMemory(buffer_size);
|
||||
if (!shared_memory)
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<SharedMemoryDesktopFrame>(
|
||||
size, size.width() * kBytesPerPixel, std::move(shared_memory));
|
||||
}
|
||||
|
||||
SharedMemoryDesktopFrame::SharedMemoryDesktopFrame(DesktopSize size,
|
||||
int stride,
|
||||
SharedMemory* shared_memory)
|
||||
: DesktopFrame(size,
|
||||
stride,
|
||||
reinterpret_cast<uint8_t*>(shared_memory->data()),
|
||||
shared_memory) {}
|
||||
|
||||
SharedMemoryDesktopFrame::SharedMemoryDesktopFrame(
|
||||
DesktopSize size,
|
||||
int stride,
|
||||
std::unique_ptr<SharedMemory> shared_memory)
|
||||
: SharedMemoryDesktopFrame(size, stride, shared_memory.release()) {}
|
||||
|
||||
SharedMemoryDesktopFrame::~SharedMemoryDesktopFrame() {
|
||||
delete shared_memory_;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
const float kStandardDPI = 96.0f;
|
||||
|
||||
// DesktopFrame represents a video frame captured from the screen.
|
||||
class RTC_EXPORT DesktopFrame {
|
||||
public:
|
||||
// DesktopFrame objects always hold BGRA data.
|
||||
static const int kBytesPerPixel = 4;
|
||||
|
||||
virtual ~DesktopFrame();
|
||||
|
||||
DesktopFrame(const DesktopFrame&) = delete;
|
||||
DesktopFrame& operator=(const DesktopFrame&) = delete;
|
||||
|
||||
// Returns the rectangle in full desktop coordinates to indicate it covers
|
||||
// the area of top_left() to top_letf() + size() / scale_factor().
|
||||
DesktopRect rect() const;
|
||||
|
||||
// Returns the scale factor from DIPs to physical pixels of the frame.
|
||||
// Assumes same scale in both X and Y directions at present.
|
||||
float scale_factor() const;
|
||||
|
||||
// Size of the frame. In physical coordinates, mapping directly from the
|
||||
// underlying buffer.
|
||||
const DesktopSize& size() const { return size_; }
|
||||
|
||||
// The top-left of the frame in full desktop coordinates. E.g. the top left
|
||||
// monitor should start from (0, 0). The desktop coordinates may be scaled by
|
||||
// OS, but this is always consistent with the MouseCursorMonitor.
|
||||
const DesktopVector& top_left() const { return top_left_; }
|
||||
void set_top_left(const DesktopVector& top_left) { top_left_ = top_left; }
|
||||
|
||||
// Distance in the buffer between two neighboring rows in bytes.
|
||||
int stride() const { return stride_; }
|
||||
|
||||
// Data buffer used for the frame.
|
||||
uint8_t* data() const { return data_; }
|
||||
|
||||
// SharedMemory used for the buffer or NULL if memory is allocated on the
|
||||
// heap. The result is guaranteed to be deleted only after the frame is
|
||||
// deleted (classes that inherit from DesktopFrame must ensure it).
|
||||
SharedMemory* shared_memory() const { return shared_memory_; }
|
||||
|
||||
// Indicates region of the screen that has changed since the previous frame.
|
||||
const DesktopRegion& updated_region() const { return updated_region_; }
|
||||
DesktopRegion* mutable_updated_region() { return &updated_region_; }
|
||||
|
||||
// DPI of the screen being captured. May be set to zero, e.g. if DPI is
|
||||
// unknown.
|
||||
const DesktopVector& dpi() const { return dpi_; }
|
||||
void set_dpi(const DesktopVector& dpi) { dpi_ = dpi; }
|
||||
|
||||
// Indicates if this frame may have the mouse cursor in it. Capturers that
|
||||
// support cursor capture may set this to true. If the cursor was
|
||||
// outside of the captured area, this may be true even though the cursor is
|
||||
// not in the image.
|
||||
bool may_contain_cursor() const { return may_contain_cursor_; }
|
||||
void set_may_contain_cursor(bool may_contain_cursor) {
|
||||
may_contain_cursor_ = may_contain_cursor;
|
||||
}
|
||||
|
||||
// Time taken to capture the frame in milliseconds.
|
||||
int64_t capture_time_ms() const { return capture_time_ms_; }
|
||||
void set_capture_time_ms(int64_t time_ms) { capture_time_ms_ = time_ms; }
|
||||
|
||||
// Copies pixels from a buffer or another frame. `dest_rect` rect must lay
|
||||
// within bounds of this frame.
|
||||
void CopyPixelsFrom(const uint8_t* src_buffer,
|
||||
int src_stride,
|
||||
const DesktopRect& dest_rect);
|
||||
void CopyPixelsFrom(const DesktopFrame& src_frame,
|
||||
const DesktopVector& src_pos,
|
||||
const DesktopRect& dest_rect);
|
||||
|
||||
// Copies pixels from another frame, with the copied & overwritten regions
|
||||
// representing the intersection between the two frames. Returns true if
|
||||
// pixels were copied, or false if there's no intersection. The scale factors
|
||||
// represent the ratios between pixel space & offset coordinate space (e.g.
|
||||
// 2.0 would indicate the frames are scaled down by 50% for display, so any
|
||||
// offset between their origins should be doubled).
|
||||
bool CopyIntersectingPixelsFrom(const DesktopFrame& src_frame,
|
||||
double horizontal_scale,
|
||||
double vertical_scale);
|
||||
|
||||
// A helper to return the data pointer of a frame at the specified position.
|
||||
uint8_t* GetFrameDataAtPos(const DesktopVector& pos) const;
|
||||
|
||||
// The DesktopCapturer implementation which generates current DesktopFrame.
|
||||
// Not all DesktopCapturer implementations set this field; it's set to
|
||||
// kUnknown by default.
|
||||
uint32_t capturer_id() const { return capturer_id_; }
|
||||
void set_capturer_id(uint32_t capturer_id) { capturer_id_ = capturer_id; }
|
||||
|
||||
// Copies various information from `other`. Anything initialized in
|
||||
// constructor are not copied.
|
||||
// This function is usually used when sharing a source DesktopFrame with
|
||||
// several clients: the original DesktopFrame should be kept unchanged. For
|
||||
// example, BasicDesktopFrame::CopyOf() and SharedDesktopFrame::Share().
|
||||
void CopyFrameInfoFrom(const DesktopFrame& other);
|
||||
|
||||
// Copies various information from `other`. Anything initialized in
|
||||
// constructor are not copied. Not like CopyFrameInfoFrom() function, this
|
||||
// function uses swap or move constructor to avoid data copy. It won't break
|
||||
// the `other`, but some of its information may be missing after this
|
||||
// operation. E.g. other->updated_region_;
|
||||
// This function is usually used when wrapping a DesktopFrame: the wrapper
|
||||
// instance takes the ownership of `other`, so other components cannot access
|
||||
// `other` anymore. For example, CroppedDesktopFrame and
|
||||
// DesktopFrameWithCursor.
|
||||
void MoveFrameInfoFrom(DesktopFrame* other);
|
||||
|
||||
// Set and get the ICC profile of the frame data pixels. Useful to build the
|
||||
// a ColorSpace object from clients of webrtc library like chromium. The
|
||||
// format of an ICC profile is defined in the following specification
|
||||
// http://www.color.org/specification/ICC1v43_2010-12.pdf.
|
||||
const std::vector<uint8_t>& icc_profile() const { return icc_profile_; }
|
||||
void set_icc_profile(const std::vector<uint8_t>& icc_profile) {
|
||||
icc_profile_ = icc_profile;
|
||||
}
|
||||
|
||||
// Sets all pixel values in the data buffer to zero.
|
||||
void SetFrameDataToBlack();
|
||||
|
||||
// Returns true if all pixel values in the data buffer are zero or false
|
||||
// otherwise. Also returns false if the frame is empty.
|
||||
bool FrameDataIsBlack() const;
|
||||
|
||||
protected:
|
||||
DesktopFrame(DesktopSize size,
|
||||
int stride,
|
||||
uint8_t* data,
|
||||
SharedMemory* shared_memory);
|
||||
|
||||
// Ownership of the buffers is defined by the classes that inherit from this
|
||||
// class. They must guarantee that the buffer is not deleted before the frame
|
||||
// is deleted.
|
||||
uint8_t* const data_;
|
||||
SharedMemory* const shared_memory_;
|
||||
|
||||
private:
|
||||
const DesktopSize size_;
|
||||
const int stride_;
|
||||
|
||||
DesktopRegion updated_region_;
|
||||
DesktopVector top_left_;
|
||||
DesktopVector dpi_;
|
||||
bool may_contain_cursor_ = false;
|
||||
int64_t capture_time_ms_;
|
||||
uint32_t capturer_id_;
|
||||
std::vector<uint8_t> icc_profile_;
|
||||
};
|
||||
|
||||
// A DesktopFrame that stores data in the heap.
|
||||
class RTC_EXPORT BasicDesktopFrame : public DesktopFrame {
|
||||
public:
|
||||
// The entire data buffer used for the frame is initialized with zeros.
|
||||
explicit BasicDesktopFrame(DesktopSize size);
|
||||
|
||||
~BasicDesktopFrame() override;
|
||||
|
||||
BasicDesktopFrame(const BasicDesktopFrame&) = delete;
|
||||
BasicDesktopFrame& operator=(const BasicDesktopFrame&) = delete;
|
||||
|
||||
// Creates a BasicDesktopFrame that contains copy of `frame`.
|
||||
// TODO(zijiehe): Return std::unique_ptr<DesktopFrame>
|
||||
static DesktopFrame* CopyOf(const DesktopFrame& frame);
|
||||
};
|
||||
|
||||
// A DesktopFrame that stores data in shared memory.
|
||||
class RTC_EXPORT SharedMemoryDesktopFrame : public DesktopFrame {
|
||||
public:
|
||||
// May return nullptr if `shared_memory_factory` failed to create a
|
||||
// SharedMemory instance.
|
||||
// `shared_memory_factory` should not be nullptr.
|
||||
static std::unique_ptr<DesktopFrame> Create(
|
||||
DesktopSize size,
|
||||
SharedMemoryFactory* shared_memory_factory);
|
||||
|
||||
// Takes ownership of `shared_memory`.
|
||||
// Deprecated, use the next constructor.
|
||||
SharedMemoryDesktopFrame(DesktopSize size,
|
||||
int stride,
|
||||
SharedMemory* shared_memory);
|
||||
|
||||
// Preferred.
|
||||
SharedMemoryDesktopFrame(DesktopSize size,
|
||||
int stride,
|
||||
std::unique_ptr<SharedMemory> shared_memory);
|
||||
|
||||
~SharedMemoryDesktopFrame() override;
|
||||
|
||||
SharedMemoryDesktopFrame(const SharedMemoryDesktopFrame&) = delete;
|
||||
SharedMemoryDesktopFrame& operator=(const SharedMemoryDesktopFrame&) = delete;
|
||||
|
||||
private:
|
||||
// Avoid unexpected order of parameter evaluation.
|
||||
// Executing both std::unique_ptr<T>::operator->() and
|
||||
// std::unique_ptr<T>::release() in the member initializer list is not safe.
|
||||
// Depends on the order of parameter evaluation,
|
||||
// std::unique_ptr<T>::operator->() may trigger assertion failure if it has
|
||||
// been evaluated after std::unique_ptr<T>::release(). By using this
|
||||
// constructor, std::unique_ptr<T>::operator->() won't be involved anymore.
|
||||
SharedMemoryDesktopFrame(DesktopRect rect,
|
||||
int stride,
|
||||
SharedMemory* shared_memory);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_H_
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame_generator.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/rgba_color.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/random.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Sets `updated_region` to `frame`. If `enlarge_updated_region` is
|
||||
// true, this function will randomly enlarge each DesktopRect in
|
||||
// `updated_region`. But the enlarged DesktopRegion won't excceed the
|
||||
// frame->size(). If `add_random_updated_region` is true, several random
|
||||
// rectangles will also be included in `frame`.
|
||||
void SetUpdatedRegion(DesktopFrame* frame,
|
||||
const DesktopRegion& updated_region,
|
||||
bool enlarge_updated_region,
|
||||
int enlarge_range,
|
||||
bool add_random_updated_region) {
|
||||
const DesktopRect screen_rect = DesktopRect::MakeSize(frame->size());
|
||||
Random random(rtc::TimeMicros());
|
||||
frame->mutable_updated_region()->Clear();
|
||||
for (DesktopRegion::Iterator it(updated_region); !it.IsAtEnd();
|
||||
it.Advance()) {
|
||||
DesktopRect rect = it.rect();
|
||||
if (enlarge_updated_region && enlarge_range > 0) {
|
||||
rect.Extend(random.Rand(enlarge_range), random.Rand(enlarge_range),
|
||||
random.Rand(enlarge_range), random.Rand(enlarge_range));
|
||||
rect.IntersectWith(screen_rect);
|
||||
}
|
||||
frame->mutable_updated_region()->AddRect(rect);
|
||||
}
|
||||
|
||||
if (add_random_updated_region) {
|
||||
for (int i = random.Rand(10); i >= 0; i--) {
|
||||
// At least a 1 x 1 updated region.
|
||||
const int left = random.Rand(0, frame->size().width() - 2);
|
||||
const int top = random.Rand(0, frame->size().height() - 2);
|
||||
const int right = random.Rand(left + 1, frame->size().width());
|
||||
const int bottom = random.Rand(top + 1, frame->size().height());
|
||||
frame->mutable_updated_region()->AddRect(
|
||||
DesktopRect::MakeLTRB(left, top, right, bottom));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Paints pixels in `rect` of `frame` to `color`.
|
||||
void PaintRect(DesktopFrame* frame, DesktopRect rect, RgbaColor rgba_color) {
|
||||
static_assert(DesktopFrame::kBytesPerPixel == sizeof(uint32_t),
|
||||
"kBytesPerPixel should be 4.");
|
||||
RTC_DCHECK_GE(frame->size().width(), rect.right());
|
||||
RTC_DCHECK_GE(frame->size().height(), rect.bottom());
|
||||
uint32_t color = rgba_color.ToUInt32();
|
||||
uint8_t* row = frame->GetFrameDataAtPos(rect.top_left());
|
||||
for (int i = 0; i < rect.height(); i++) {
|
||||
uint32_t* column = reinterpret_cast<uint32_t*>(row);
|
||||
for (int j = 0; j < rect.width(); j++) {
|
||||
column[j] = color;
|
||||
}
|
||||
row += frame->stride();
|
||||
}
|
||||
}
|
||||
|
||||
// Paints pixels in `region` of `frame` to `color`.
|
||||
void PaintRegion(DesktopFrame* frame,
|
||||
DesktopRegion* region,
|
||||
RgbaColor rgba_color) {
|
||||
region->IntersectWith(DesktopRect::MakeSize(frame->size()));
|
||||
for (DesktopRegion::Iterator it(*region); !it.IsAtEnd(); it.Advance()) {
|
||||
PaintRect(frame, it.rect(), rgba_color);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DesktopFrameGenerator::DesktopFrameGenerator() {}
|
||||
DesktopFrameGenerator::~DesktopFrameGenerator() {}
|
||||
|
||||
DesktopFramePainter::DesktopFramePainter() {}
|
||||
DesktopFramePainter::~DesktopFramePainter() {}
|
||||
|
||||
PainterDesktopFrameGenerator::PainterDesktopFrameGenerator()
|
||||
: size_(1024, 768),
|
||||
return_frame_(true),
|
||||
provide_updated_region_hints_(false),
|
||||
enlarge_updated_region_(false),
|
||||
enlarge_range_(20),
|
||||
add_random_updated_region_(false),
|
||||
painter_(nullptr) {}
|
||||
PainterDesktopFrameGenerator::~PainterDesktopFrameGenerator() {}
|
||||
|
||||
std::unique_ptr<DesktopFrame> PainterDesktopFrameGenerator::GetNextFrame(
|
||||
SharedMemoryFactory* factory) {
|
||||
if (!return_frame_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> frame = std::unique_ptr<DesktopFrame>(
|
||||
factory ? SharedMemoryDesktopFrame::Create(size_, factory).release()
|
||||
: new BasicDesktopFrame(size_));
|
||||
if (painter_) {
|
||||
DesktopRegion updated_region;
|
||||
if (!painter_->Paint(frame.get(), &updated_region)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (provide_updated_region_hints_) {
|
||||
SetUpdatedRegion(frame.get(), updated_region, enlarge_updated_region_,
|
||||
enlarge_range_, add_random_updated_region_);
|
||||
} else {
|
||||
frame->mutable_updated_region()->SetRect(
|
||||
DesktopRect::MakeSize(frame->size()));
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
DesktopSize* PainterDesktopFrameGenerator::size() {
|
||||
return &size_;
|
||||
}
|
||||
|
||||
void PainterDesktopFrameGenerator::set_return_frame(bool return_frame) {
|
||||
return_frame_ = return_frame;
|
||||
}
|
||||
|
||||
void PainterDesktopFrameGenerator::set_provide_updated_region_hints(
|
||||
bool provide_updated_region_hints) {
|
||||
provide_updated_region_hints_ = provide_updated_region_hints;
|
||||
}
|
||||
|
||||
void PainterDesktopFrameGenerator::set_enlarge_updated_region(
|
||||
bool enlarge_updated_region) {
|
||||
enlarge_updated_region_ = enlarge_updated_region;
|
||||
}
|
||||
|
||||
void PainterDesktopFrameGenerator::set_enlarge_range(int enlarge_range) {
|
||||
enlarge_range_ = enlarge_range;
|
||||
}
|
||||
|
||||
void PainterDesktopFrameGenerator::set_add_random_updated_region(
|
||||
bool add_random_updated_region) {
|
||||
add_random_updated_region_ = add_random_updated_region;
|
||||
}
|
||||
|
||||
void PainterDesktopFrameGenerator::set_desktop_frame_painter(
|
||||
DesktopFramePainter* painter) {
|
||||
painter_ = painter;
|
||||
}
|
||||
|
||||
BlackWhiteDesktopFramePainter::BlackWhiteDesktopFramePainter() {}
|
||||
BlackWhiteDesktopFramePainter::~BlackWhiteDesktopFramePainter() {}
|
||||
|
||||
DesktopRegion* BlackWhiteDesktopFramePainter::updated_region() {
|
||||
return &updated_region_;
|
||||
}
|
||||
|
||||
bool BlackWhiteDesktopFramePainter::Paint(DesktopFrame* frame,
|
||||
DesktopRegion* updated_region) {
|
||||
RTC_DCHECK(updated_region->is_empty());
|
||||
memset(frame->data(), 0, frame->stride() * frame->size().height());
|
||||
PaintRegion(frame, &updated_region_, RgbaColor(0xFFFFFFFF));
|
||||
updated_region_.Swap(updated_region);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_GENERATOR_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_GENERATOR_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// An interface to generate a DesktopFrame.
|
||||
class DesktopFrameGenerator {
|
||||
public:
|
||||
DesktopFrameGenerator();
|
||||
virtual ~DesktopFrameGenerator();
|
||||
|
||||
virtual std::unique_ptr<DesktopFrame> GetNextFrame(
|
||||
SharedMemoryFactory* factory) = 0;
|
||||
};
|
||||
|
||||
// An interface to paint a DesktopFrame. This interface is used by
|
||||
// PainterDesktopFrameGenerator.
|
||||
class DesktopFramePainter {
|
||||
public:
|
||||
DesktopFramePainter();
|
||||
virtual ~DesktopFramePainter();
|
||||
|
||||
virtual bool Paint(DesktopFrame* frame, DesktopRegion* updated_region) = 0;
|
||||
};
|
||||
|
||||
// An implementation of DesktopFrameGenerator to take care about the
|
||||
// DesktopFrame size, filling updated_region(), etc, but leaves the real
|
||||
// painting work to a DesktopFramePainter implementation.
|
||||
class PainterDesktopFrameGenerator final : public DesktopFrameGenerator {
|
||||
public:
|
||||
PainterDesktopFrameGenerator();
|
||||
~PainterDesktopFrameGenerator() override;
|
||||
|
||||
std::unique_ptr<DesktopFrame> GetNextFrame(
|
||||
SharedMemoryFactory* factory) override;
|
||||
|
||||
// Sets the size of the frame which will be returned in next GetNextFrame()
|
||||
// call.
|
||||
DesktopSize* size();
|
||||
|
||||
// Decides whether BaseDesktopFrameGenerator returns a frame in next Capture()
|
||||
// callback. If return_frame_ is true, BaseDesktopFrameGenerator will create a
|
||||
// frame according to both size_ and SharedMemoryFactory input, and uses
|
||||
// Paint() function to paint it.
|
||||
void set_return_frame(bool return_frame);
|
||||
|
||||
// Decides whether MockScreenCapturer returns a frame with updated regions.
|
||||
// MockScreenCapturer will keep DesktopFrame::updated_region() empty if this
|
||||
// field is false.
|
||||
void set_provide_updated_region_hints(bool provide_updated_region_hints);
|
||||
|
||||
// Decides whether MockScreenCapturer randomly enlarges updated regions in the
|
||||
// DesktopFrame. Set this field to true to simulate an inaccurate updated
|
||||
// regions' return from OS APIs.
|
||||
void set_enlarge_updated_region(bool enlarge_updated_region);
|
||||
|
||||
// The range to enlarge a updated region if `enlarge_updated_region_` is true.
|
||||
// If this field is less than zero, it will be treated as zero, and
|
||||
// `enlarge_updated_region_` will be ignored.
|
||||
void set_enlarge_range(int enlarge_range);
|
||||
|
||||
// Decides whether BaseDesktopFrameGenerator randomly add some updated regions
|
||||
// in the DesktopFrame. Set this field to true to simulate an inaccurate
|
||||
// updated regions' return from OS APIs.
|
||||
void set_add_random_updated_region(bool add_random_updated_region);
|
||||
|
||||
// Sets the painter object to do the real painting work, if no `painter_` has
|
||||
// been set to this instance, the DesktopFrame returned by GetNextFrame()
|
||||
// function will keep in an undefined but valid state.
|
||||
// PainterDesktopFrameGenerator does not take ownership of the `painter`.
|
||||
void set_desktop_frame_painter(DesktopFramePainter* painter);
|
||||
|
||||
private:
|
||||
DesktopSize size_;
|
||||
bool return_frame_;
|
||||
bool provide_updated_region_hints_;
|
||||
bool enlarge_updated_region_;
|
||||
int enlarge_range_;
|
||||
bool add_random_updated_region_;
|
||||
DesktopFramePainter* painter_;
|
||||
};
|
||||
|
||||
// An implementation of DesktopFramePainter to paint black on
|
||||
// mutable_updated_region(), and white elsewhere.
|
||||
class BlackWhiteDesktopFramePainter final : public DesktopFramePainter {
|
||||
public:
|
||||
BlackWhiteDesktopFramePainter();
|
||||
~BlackWhiteDesktopFramePainter() override;
|
||||
|
||||
// The black regions of the frame which will be returned in next Paint()
|
||||
// call. BlackWhiteDesktopFramePainter will draw a white frame, with black
|
||||
// in the updated_region_. Each Paint() call will consume updated_region_.
|
||||
DesktopRegion* updated_region();
|
||||
|
||||
// DesktopFramePainter interface.
|
||||
bool Paint(DesktopFrame* frame, DesktopRegion* updated_region) override;
|
||||
|
||||
private:
|
||||
DesktopRegion updated_region_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_GENERATOR_H_
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame_rotation.h"
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
#include "third_party/libyuv/include/libyuv/rotate_argb.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
libyuv::RotationMode ToLibyuvRotationMode(Rotation rotation) {
|
||||
switch (rotation) {
|
||||
case Rotation::CLOCK_WISE_0:
|
||||
return libyuv::kRotate0;
|
||||
case Rotation::CLOCK_WISE_90:
|
||||
return libyuv::kRotate90;
|
||||
case Rotation::CLOCK_WISE_180:
|
||||
return libyuv::kRotate180;
|
||||
case Rotation::CLOCK_WISE_270:
|
||||
return libyuv::kRotate270;
|
||||
}
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return libyuv::kRotate0;
|
||||
}
|
||||
|
||||
DesktopRect RotateAndOffsetRect(DesktopRect rect,
|
||||
DesktopSize size,
|
||||
Rotation rotation,
|
||||
DesktopVector offset) {
|
||||
DesktopRect result = RotateRect(rect, size, rotation);
|
||||
result.Translate(offset);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Rotation ReverseRotation(Rotation rotation) {
|
||||
switch (rotation) {
|
||||
case Rotation::CLOCK_WISE_0:
|
||||
return rotation;
|
||||
case Rotation::CLOCK_WISE_90:
|
||||
return Rotation::CLOCK_WISE_270;
|
||||
case Rotation::CLOCK_WISE_180:
|
||||
return Rotation::CLOCK_WISE_180;
|
||||
case Rotation::CLOCK_WISE_270:
|
||||
return Rotation::CLOCK_WISE_90;
|
||||
}
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return Rotation::CLOCK_WISE_0;
|
||||
}
|
||||
|
||||
DesktopSize RotateSize(DesktopSize size, Rotation rotation) {
|
||||
switch (rotation) {
|
||||
case Rotation::CLOCK_WISE_0:
|
||||
case Rotation::CLOCK_WISE_180:
|
||||
return size;
|
||||
case Rotation::CLOCK_WISE_90:
|
||||
case Rotation::CLOCK_WISE_270:
|
||||
return DesktopSize(size.height(), size.width());
|
||||
}
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return DesktopSize();
|
||||
}
|
||||
|
||||
DesktopRect RotateRect(DesktopRect rect, DesktopSize size, Rotation rotation) {
|
||||
switch (rotation) {
|
||||
case Rotation::CLOCK_WISE_0:
|
||||
return rect;
|
||||
case Rotation::CLOCK_WISE_90:
|
||||
return DesktopRect::MakeXYWH(size.height() - rect.bottom(), rect.left(),
|
||||
rect.height(), rect.width());
|
||||
case Rotation::CLOCK_WISE_180:
|
||||
return DesktopRect::MakeXYWH(size.width() - rect.right(),
|
||||
size.height() - rect.bottom(), rect.width(),
|
||||
rect.height());
|
||||
case Rotation::CLOCK_WISE_270:
|
||||
return DesktopRect::MakeXYWH(rect.top(), size.width() - rect.right(),
|
||||
rect.height(), rect.width());
|
||||
}
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
return DesktopRect();
|
||||
}
|
||||
|
||||
void RotateDesktopFrame(const DesktopFrame& source,
|
||||
const DesktopRect& source_rect,
|
||||
const Rotation& rotation,
|
||||
const DesktopVector& target_offset,
|
||||
DesktopFrame* target) {
|
||||
RTC_DCHECK(target);
|
||||
RTC_DCHECK(DesktopRect::MakeSize(source.size()).ContainsRect(source_rect));
|
||||
// The rectangle in `target`.
|
||||
const DesktopRect target_rect =
|
||||
RotateAndOffsetRect(source_rect, source.size(), rotation, target_offset);
|
||||
RTC_DCHECK(DesktopRect::MakeSize(target->size()).ContainsRect(target_rect));
|
||||
|
||||
if (target_rect.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int result = libyuv::ARGBRotate(
|
||||
source.GetFrameDataAtPos(source_rect.top_left()), source.stride(),
|
||||
target->GetFrameDataAtPos(target_rect.top_left()), target->stride(),
|
||||
source_rect.width(), source_rect.height(),
|
||||
ToLibyuvRotationMode(rotation));
|
||||
RTC_DCHECK_EQ(result, 0);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_ROTATION_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_ROTATION_H_
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Represents the rotation of a DesktopFrame.
|
||||
enum class Rotation {
|
||||
CLOCK_WISE_0,
|
||||
CLOCK_WISE_90,
|
||||
CLOCK_WISE_180,
|
||||
CLOCK_WISE_270,
|
||||
};
|
||||
|
||||
// Rotates input DesktopFrame `source`, copies pixel in an unrotated rectangle
|
||||
// `source_rect` into the target rectangle of another DesktopFrame `target`.
|
||||
// Target rectangle here is the rotated `source_rect` plus `target_offset`.
|
||||
// `rotation` specifies `source` to `target` rotation. `source_rect` is in
|
||||
// `source` coordinate. `target_offset` is in `target` coordinate.
|
||||
// This function triggers check failure if `source` does not cover the
|
||||
// `source_rect`, or `target` does not cover the rotated `rect`.
|
||||
void RotateDesktopFrame(const DesktopFrame& source,
|
||||
const DesktopRect& source_rect,
|
||||
const Rotation& rotation,
|
||||
const DesktopVector& target_offset,
|
||||
DesktopFrame* target);
|
||||
|
||||
// Returns a reverse rotation of `rotation`.
|
||||
Rotation ReverseRotation(Rotation rotation);
|
||||
|
||||
// Returns a rotated DesktopSize of `size`.
|
||||
DesktopSize RotateSize(DesktopSize size, Rotation rotation);
|
||||
|
||||
// Returns a rotated DesktopRect of `rect`. The `size` represents the size of
|
||||
// the DesktopFrame which `rect` belongs in.
|
||||
DesktopRect RotateRect(DesktopRect rect, DesktopSize size, Rotation rotation);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_ROTATION_H_
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame_win.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DesktopFrameWin::DesktopFrameWin(DesktopSize size,
|
||||
int stride,
|
||||
uint8_t* data,
|
||||
std::unique_ptr<SharedMemory> shared_memory,
|
||||
HBITMAP bitmap)
|
||||
: DesktopFrame(size, stride, data, shared_memory.get()),
|
||||
bitmap_(bitmap),
|
||||
owned_shared_memory_(std::move(shared_memory)) {}
|
||||
|
||||
DesktopFrameWin::~DesktopFrameWin() {
|
||||
DeleteObject(bitmap_);
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopFrameWin> DesktopFrameWin::Create(
|
||||
DesktopSize size,
|
||||
SharedMemoryFactory* shared_memory_factory,
|
||||
HDC hdc) {
|
||||
int bytes_per_row = size.width() * kBytesPerPixel;
|
||||
int buffer_size = bytes_per_row * size.height();
|
||||
|
||||
// Describe a device independent bitmap (DIB) that is the size of the desktop.
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biHeight = -size.height();
|
||||
bmi.bmiHeader.biWidth = size.width();
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = DesktopFrameWin::kBytesPerPixel * 8;
|
||||
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
|
||||
bmi.bmiHeader.biSizeImage = bytes_per_row * size.height();
|
||||
|
||||
std::unique_ptr<SharedMemory> shared_memory;
|
||||
HANDLE section_handle = nullptr;
|
||||
if (shared_memory_factory) {
|
||||
shared_memory = shared_memory_factory->CreateSharedMemory(buffer_size);
|
||||
if (!shared_memory) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to allocate shared memory";
|
||||
return nullptr;
|
||||
}
|
||||
section_handle = shared_memory->handle();
|
||||
}
|
||||
void* data = nullptr;
|
||||
HBITMAP bitmap =
|
||||
CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &data, section_handle, 0);
|
||||
if (!bitmap) {
|
||||
RTC_LOG(LS_WARNING) << "Failed to allocate new window frame "
|
||||
<< GetLastError();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::unique_ptr<DesktopFrameWin>(
|
||||
new DesktopFrameWin(size, bytes_per_row, reinterpret_cast<uint8_t*>(data),
|
||||
std::move(shared_memory), bitmap));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// DesktopFrame implementation used by screen and window captures on Windows.
|
||||
// Frame data is stored in a GDI bitmap.
|
||||
class DesktopFrameWin : public DesktopFrame {
|
||||
public:
|
||||
~DesktopFrameWin() override;
|
||||
|
||||
DesktopFrameWin(const DesktopFrameWin&) = delete;
|
||||
DesktopFrameWin& operator=(const DesktopFrameWin&) = delete;
|
||||
|
||||
static std::unique_ptr<DesktopFrameWin>
|
||||
Create(DesktopSize size, SharedMemoryFactory* shared_memory_factory, HDC hdc);
|
||||
|
||||
HBITMAP bitmap() { return bitmap_; }
|
||||
|
||||
private:
|
||||
DesktopFrameWin(DesktopSize size,
|
||||
int stride,
|
||||
uint8_t* data,
|
||||
std::unique_ptr<SharedMemory> shared_memory,
|
||||
HBITMAP bitmap);
|
||||
|
||||
HBITMAP bitmap_;
|
||||
std::unique_ptr<SharedMemory> owned_shared_memory_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_FRAME_WIN_H_
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
bool DesktopRect::Contains(const DesktopVector& point) const {
|
||||
return point.x() >= left() && point.x() < right() && point.y() >= top() &&
|
||||
point.y() < bottom();
|
||||
}
|
||||
|
||||
bool DesktopRect::ContainsRect(const DesktopRect& rect) const {
|
||||
return rect.left() >= left() && rect.right() <= right() &&
|
||||
rect.top() >= top() && rect.bottom() <= bottom();
|
||||
}
|
||||
|
||||
void DesktopRect::IntersectWith(const DesktopRect& rect) {
|
||||
left_ = std::max(left(), rect.left());
|
||||
top_ = std::max(top(), rect.top());
|
||||
right_ = std::min(right(), rect.right());
|
||||
bottom_ = std::min(bottom(), rect.bottom());
|
||||
if (is_empty()) {
|
||||
left_ = 0;
|
||||
top_ = 0;
|
||||
right_ = 0;
|
||||
bottom_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopRect::UnionWith(const DesktopRect& rect) {
|
||||
if (is_empty()) {
|
||||
*this = rect;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rect.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
left_ = std::min(left(), rect.left());
|
||||
top_ = std::min(top(), rect.top());
|
||||
right_ = std::max(right(), rect.right());
|
||||
bottom_ = std::max(bottom(), rect.bottom());
|
||||
}
|
||||
|
||||
void DesktopRect::Translate(int32_t dx, int32_t dy) {
|
||||
left_ += dx;
|
||||
top_ += dy;
|
||||
right_ += dx;
|
||||
bottom_ += dy;
|
||||
}
|
||||
|
||||
void DesktopRect::Extend(int32_t left_offset,
|
||||
int32_t top_offset,
|
||||
int32_t right_offset,
|
||||
int32_t bottom_offset) {
|
||||
left_ -= left_offset;
|
||||
top_ -= top_offset;
|
||||
right_ += right_offset;
|
||||
bottom_ += bottom_offset;
|
||||
}
|
||||
|
||||
void DesktopRect::Scale(double horizontal, double vertical) {
|
||||
right_ += static_cast<int>(std::round(width() * (horizontal - 1)));
|
||||
bottom_ += static_cast<int>(std::round(height() * (vertical - 1)));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A vector in the 2D integer space. E.g. can be used to represent screen DPI.
|
||||
class DesktopVector {
|
||||
public:
|
||||
DesktopVector() : x_(0), y_(0) {}
|
||||
DesktopVector(int32_t x, int32_t y) : x_(x), y_(y) {}
|
||||
|
||||
int32_t x() const { return x_; }
|
||||
int32_t y() const { return y_; }
|
||||
bool is_zero() const { return x_ == 0 && y_ == 0; }
|
||||
|
||||
bool equals(const DesktopVector& other) const {
|
||||
return x_ == other.x_ && y_ == other.y_;
|
||||
}
|
||||
|
||||
void set(int32_t x, int32_t y) {
|
||||
x_ = x;
|
||||
y_ = y;
|
||||
}
|
||||
|
||||
DesktopVector add(const DesktopVector& other) const {
|
||||
return DesktopVector(x() + other.x(), y() + other.y());
|
||||
}
|
||||
DesktopVector subtract(const DesktopVector& other) const {
|
||||
return DesktopVector(x() - other.x(), y() - other.y());
|
||||
}
|
||||
|
||||
DesktopVector operator-() const { return DesktopVector(-x_, -y_); }
|
||||
|
||||
private:
|
||||
int32_t x_;
|
||||
int32_t y_;
|
||||
};
|
||||
|
||||
// Type used to represent screen/window size.
|
||||
class DesktopSize {
|
||||
public:
|
||||
DesktopSize() : width_(0), height_(0) {}
|
||||
DesktopSize(int32_t width, int32_t height) : width_(width), height_(height) {}
|
||||
|
||||
int32_t width() const { return width_; }
|
||||
int32_t height() const { return height_; }
|
||||
|
||||
bool is_empty() const { return width_ <= 0 || height_ <= 0; }
|
||||
|
||||
bool equals(const DesktopSize& other) const {
|
||||
return width_ == other.width_ && height_ == other.height_;
|
||||
}
|
||||
|
||||
void set(int32_t width, int32_t height) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t width_;
|
||||
int32_t height_;
|
||||
};
|
||||
|
||||
// Represents a rectangle on the screen.
|
||||
class RTC_EXPORT DesktopRect {
|
||||
public:
|
||||
static DesktopRect MakeSize(const DesktopSize& size) {
|
||||
return DesktopRect(0, 0, size.width(), size.height());
|
||||
}
|
||||
static DesktopRect MakeWH(int32_t width, int32_t height) {
|
||||
return DesktopRect(0, 0, width, height);
|
||||
}
|
||||
static DesktopRect MakeXYWH(int32_t x,
|
||||
int32_t y,
|
||||
int32_t width,
|
||||
int32_t height) {
|
||||
return DesktopRect(x, y, x + width, y + height);
|
||||
}
|
||||
static DesktopRect MakeLTRB(int32_t left,
|
||||
int32_t top,
|
||||
int32_t right,
|
||||
int32_t bottom) {
|
||||
return DesktopRect(left, top, right, bottom);
|
||||
}
|
||||
static DesktopRect MakeOriginSize(const DesktopVector& origin,
|
||||
const DesktopSize& size) {
|
||||
return MakeXYWH(origin.x(), origin.y(), size.width(), size.height());
|
||||
}
|
||||
|
||||
DesktopRect() : left_(0), top_(0), right_(0), bottom_(0) {}
|
||||
|
||||
int32_t left() const { return left_; }
|
||||
int32_t top() const { return top_; }
|
||||
int32_t right() const { return right_; }
|
||||
int32_t bottom() const { return bottom_; }
|
||||
int32_t width() const { return right_ - left_; }
|
||||
int32_t height() const { return bottom_ - top_; }
|
||||
|
||||
void set_width(int32_t width) { right_ = left_ + width; }
|
||||
void set_height(int32_t height) { bottom_ = top_ + height; }
|
||||
|
||||
DesktopVector top_left() const { return DesktopVector(left_, top_); }
|
||||
DesktopSize size() const { return DesktopSize(width(), height()); }
|
||||
|
||||
bool is_empty() const { return left_ >= right_ || top_ >= bottom_; }
|
||||
|
||||
bool equals(const DesktopRect& other) const {
|
||||
return left_ == other.left_ && top_ == other.top_ &&
|
||||
right_ == other.right_ && bottom_ == other.bottom_;
|
||||
}
|
||||
|
||||
// Returns true if `point` lies within the rectangle boundaries.
|
||||
bool Contains(const DesktopVector& point) const;
|
||||
|
||||
// Returns true if `rect` lies within the boundaries of this rectangle.
|
||||
bool ContainsRect(const DesktopRect& rect) const;
|
||||
|
||||
// Finds intersection with `rect`.
|
||||
void IntersectWith(const DesktopRect& rect);
|
||||
|
||||
// Extends the rectangle to cover `rect`. If `this` is empty, replaces `this`
|
||||
// with `rect`; if `rect` is empty, this function takes no effect.
|
||||
void UnionWith(const DesktopRect& rect);
|
||||
|
||||
// Adds (dx, dy) to the position of the rectangle.
|
||||
void Translate(int32_t dx, int32_t dy);
|
||||
void Translate(DesktopVector d) { Translate(d.x(), d.y()); }
|
||||
|
||||
// Enlarges current DesktopRect by subtracting `left_offset` and `top_offset`
|
||||
// from `left_` and `top_`, and adding `right_offset` and `bottom_offset` to
|
||||
// `right_` and `bottom_`. This function does not normalize the result, so
|
||||
// `left_` and `top_` may be less than zero or larger than `right_` and
|
||||
// `bottom_`.
|
||||
void Extend(int32_t left_offset,
|
||||
int32_t top_offset,
|
||||
int32_t right_offset,
|
||||
int32_t bottom_offset);
|
||||
|
||||
// Scales current DesktopRect. This function does not impact the `top_` and
|
||||
// `left_`.
|
||||
void Scale(double horizontal, double vertical);
|
||||
|
||||
private:
|
||||
DesktopRect(int32_t left, int32_t top, int32_t right, int32_t bottom)
|
||||
: left_(left), top_(top), right_(right), bottom_(bottom) {}
|
||||
|
||||
int32_t left_;
|
||||
int32_t top_;
|
||||
int32_t right_;
|
||||
int32_t bottom_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_GEOMETRY_H_
|
||||
|
|
@ -0,0 +1,567 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DesktopRegion::RowSpan::RowSpan(int32_t left, int32_t right)
|
||||
: left(left), right(right) {}
|
||||
|
||||
DesktopRegion::Row::Row(const Row&) = default;
|
||||
DesktopRegion::Row::Row(Row&&) = default;
|
||||
|
||||
DesktopRegion::Row::Row(int32_t top, int32_t bottom)
|
||||
: top(top), bottom(bottom) {}
|
||||
|
||||
DesktopRegion::Row::~Row() {}
|
||||
|
||||
DesktopRegion::DesktopRegion() {}
|
||||
|
||||
DesktopRegion::DesktopRegion(const DesktopRect& rect) {
|
||||
AddRect(rect);
|
||||
}
|
||||
|
||||
DesktopRegion::DesktopRegion(const DesktopRect* rects, int count) {
|
||||
AddRects(rects, count);
|
||||
}
|
||||
|
||||
DesktopRegion::DesktopRegion(const DesktopRegion& other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
DesktopRegion::~DesktopRegion() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
DesktopRegion& DesktopRegion::operator=(const DesktopRegion& other) {
|
||||
Clear();
|
||||
rows_ = other.rows_;
|
||||
for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) {
|
||||
// Copy each row.
|
||||
Row* row = it->second;
|
||||
it->second = new Row(*row);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool DesktopRegion::Equals(const DesktopRegion& region) const {
|
||||
// Iterate over rows of the tow regions and compare each row.
|
||||
Rows::const_iterator it1 = rows_.begin();
|
||||
Rows::const_iterator it2 = region.rows_.begin();
|
||||
while (it1 != rows_.end()) {
|
||||
if (it2 == region.rows_.end() || it1->first != it2->first ||
|
||||
it1->second->top != it2->second->top ||
|
||||
it1->second->bottom != it2->second->bottom ||
|
||||
it1->second->spans != it2->second->spans) {
|
||||
return false;
|
||||
}
|
||||
++it1;
|
||||
++it2;
|
||||
}
|
||||
return it2 == region.rows_.end();
|
||||
}
|
||||
|
||||
void DesktopRegion::Clear() {
|
||||
for (Rows::iterator row = rows_.begin(); row != rows_.end(); ++row) {
|
||||
delete row->second;
|
||||
}
|
||||
rows_.clear();
|
||||
}
|
||||
|
||||
void DesktopRegion::SetRect(const DesktopRect& rect) {
|
||||
Clear();
|
||||
AddRect(rect);
|
||||
}
|
||||
|
||||
void DesktopRegion::AddRect(const DesktopRect& rect) {
|
||||
if (rect.is_empty())
|
||||
return;
|
||||
|
||||
// Top of the part of the `rect` that hasn't been inserted yet. Increased as
|
||||
// we iterate over the rows until it reaches `rect.bottom()`.
|
||||
int top = rect.top();
|
||||
|
||||
// Iterate over all rows that may intersect with `rect` and add new rows when
|
||||
// necessary.
|
||||
Rows::iterator row = rows_.upper_bound(top);
|
||||
while (top < rect.bottom()) {
|
||||
if (row == rows_.end() || top < row->second->top) {
|
||||
// If `top` is above the top of the current `row` then add a new row above
|
||||
// the current one.
|
||||
int32_t bottom = rect.bottom();
|
||||
if (row != rows_.end() && row->second->top < bottom)
|
||||
bottom = row->second->top;
|
||||
row = rows_.insert(row, Rows::value_type(bottom, new Row(top, bottom)));
|
||||
} else if (top > row->second->top) {
|
||||
// If the `top` falls in the middle of the `row` then split `row` into
|
||||
// two, at `top`, and leave `row` referring to the lower of the two,
|
||||
// ready to insert a new span into.
|
||||
RTC_DCHECK_LE(top, row->second->bottom);
|
||||
Rows::iterator new_row = rows_.insert(
|
||||
row, Rows::value_type(top, new Row(row->second->top, top)));
|
||||
row->second->top = top;
|
||||
new_row->second->spans = row->second->spans;
|
||||
}
|
||||
|
||||
if (rect.bottom() < row->second->bottom) {
|
||||
// If the bottom of the `rect` falls in the middle of the `row` split
|
||||
// `row` into two, at `top`, and leave `row` referring to the upper of
|
||||
// the two, ready to insert a new span into.
|
||||
Rows::iterator new_row = rows_.insert(
|
||||
row, Rows::value_type(rect.bottom(), new Row(top, rect.bottom())));
|
||||
row->second->top = rect.bottom();
|
||||
new_row->second->spans = row->second->spans;
|
||||
row = new_row;
|
||||
}
|
||||
|
||||
// Add a new span to the current row.
|
||||
AddSpanToRow(row->second, rect.left(), rect.right());
|
||||
top = row->second->bottom;
|
||||
|
||||
MergeWithPrecedingRow(row);
|
||||
|
||||
// Move to the next row.
|
||||
++row;
|
||||
}
|
||||
|
||||
if (row != rows_.end())
|
||||
MergeWithPrecedingRow(row);
|
||||
}
|
||||
|
||||
void DesktopRegion::AddRects(const DesktopRect* rects, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
AddRect(rects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopRegion::MergeWithPrecedingRow(Rows::iterator row) {
|
||||
RTC_DCHECK(row != rows_.end());
|
||||
|
||||
if (row != rows_.begin()) {
|
||||
Rows::iterator previous_row = row;
|
||||
previous_row--;
|
||||
|
||||
// If `row` and `previous_row` are next to each other and contain the same
|
||||
// set of spans then they can be merged.
|
||||
if (previous_row->second->bottom == row->second->top &&
|
||||
previous_row->second->spans == row->second->spans) {
|
||||
row->second->top = previous_row->second->top;
|
||||
delete previous_row->second;
|
||||
rows_.erase(previous_row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopRegion::AddRegion(const DesktopRegion& region) {
|
||||
// TODO(sergeyu): This function is not optimized - potentially it can iterate
|
||||
// over rows of the two regions similar to how it works in Intersect().
|
||||
for (Iterator it(region); !it.IsAtEnd(); it.Advance()) {
|
||||
AddRect(it.rect());
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopRegion::Intersect(const DesktopRegion& region1,
|
||||
const DesktopRegion& region2) {
|
||||
Clear();
|
||||
|
||||
Rows::const_iterator it1 = region1.rows_.begin();
|
||||
Rows::const_iterator end1 = region1.rows_.end();
|
||||
Rows::const_iterator it2 = region2.rows_.begin();
|
||||
Rows::const_iterator end2 = region2.rows_.end();
|
||||
if (it1 == end1 || it2 == end2)
|
||||
return;
|
||||
|
||||
while (it1 != end1 && it2 != end2) {
|
||||
// Arrange for `it1` to always be the top-most of the rows.
|
||||
if (it2->second->top < it1->second->top) {
|
||||
std::swap(it1, it2);
|
||||
std::swap(end1, end2);
|
||||
}
|
||||
|
||||
// Skip `it1` if it doesn't intersect `it2` at all.
|
||||
if (it1->second->bottom <= it2->second->top) {
|
||||
++it1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Top of the `it1` row is above the top of `it2`, so top of the
|
||||
// intersection is always the top of `it2`.
|
||||
int32_t top = it2->second->top;
|
||||
int32_t bottom = std::min(it1->second->bottom, it2->second->bottom);
|
||||
|
||||
Rows::iterator new_row = rows_.insert(
|
||||
rows_.end(), Rows::value_type(bottom, new Row(top, bottom)));
|
||||
IntersectRows(it1->second->spans, it2->second->spans,
|
||||
&new_row->second->spans);
|
||||
if (new_row->second->spans.empty()) {
|
||||
delete new_row->second;
|
||||
rows_.erase(new_row);
|
||||
} else {
|
||||
MergeWithPrecedingRow(new_row);
|
||||
}
|
||||
|
||||
// If `it1` was completely consumed, move to the next one.
|
||||
if (it1->second->bottom == bottom)
|
||||
++it1;
|
||||
// If `it2` was completely consumed, move to the next one.
|
||||
if (it2->second->bottom == bottom)
|
||||
++it2;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void DesktopRegion::IntersectRows(const RowSpanSet& set1,
|
||||
const RowSpanSet& set2,
|
||||
RowSpanSet* output) {
|
||||
RowSpanSet::const_iterator it1 = set1.begin();
|
||||
RowSpanSet::const_iterator end1 = set1.end();
|
||||
RowSpanSet::const_iterator it2 = set2.begin();
|
||||
RowSpanSet::const_iterator end2 = set2.end();
|
||||
RTC_DCHECK(it1 != end1 && it2 != end2);
|
||||
|
||||
do {
|
||||
// Arrange for `it1` to always be the left-most of the spans.
|
||||
if (it2->left < it1->left) {
|
||||
std::swap(it1, it2);
|
||||
std::swap(end1, end2);
|
||||
}
|
||||
|
||||
// Skip `it1` if it doesn't intersect `it2` at all.
|
||||
if (it1->right <= it2->left) {
|
||||
++it1;
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t left = it2->left;
|
||||
int32_t right = std::min(it1->right, it2->right);
|
||||
RTC_DCHECK_LT(left, right);
|
||||
|
||||
output->push_back(RowSpan(left, right));
|
||||
|
||||
// If `it1` was completely consumed, move to the next one.
|
||||
if (it1->right == right)
|
||||
++it1;
|
||||
// If `it2` was completely consumed, move to the next one.
|
||||
if (it2->right == right)
|
||||
++it2;
|
||||
} while (it1 != end1 && it2 != end2);
|
||||
}
|
||||
|
||||
void DesktopRegion::IntersectWith(const DesktopRegion& region) {
|
||||
DesktopRegion old_region;
|
||||
Swap(&old_region);
|
||||
Intersect(old_region, region);
|
||||
}
|
||||
|
||||
void DesktopRegion::IntersectWith(const DesktopRect& rect) {
|
||||
DesktopRegion region;
|
||||
region.AddRect(rect);
|
||||
IntersectWith(region);
|
||||
}
|
||||
|
||||
void DesktopRegion::Subtract(const DesktopRegion& region) {
|
||||
if (region.rows_.empty())
|
||||
return;
|
||||
|
||||
// `row_b` refers to the current row being subtracted.
|
||||
Rows::const_iterator row_b = region.rows_.begin();
|
||||
|
||||
// Current vertical position at which subtraction is happening.
|
||||
int top = row_b->second->top;
|
||||
|
||||
// `row_a` refers to the current row we are subtracting from. Skip all rows
|
||||
// above `top`.
|
||||
Rows::iterator row_a = rows_.upper_bound(top);
|
||||
|
||||
// Step through rows of the both regions subtracting content of `row_b` from
|
||||
// `row_a`.
|
||||
while (row_a != rows_.end() && row_b != region.rows_.end()) {
|
||||
// Skip `row_a` if it doesn't intersect with the `row_b`.
|
||||
if (row_a->second->bottom <= top) {
|
||||
// Each output row is merged with previously-processed rows before further
|
||||
// rows are processed.
|
||||
MergeWithPrecedingRow(row_a);
|
||||
++row_a;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (top > row_a->second->top) {
|
||||
// If `top` falls in the middle of `row_a` then split `row_a` into two, at
|
||||
// `top`, and leave `row_a` referring to the lower of the two, ready to
|
||||
// subtract spans from.
|
||||
RTC_DCHECK_LE(top, row_a->second->bottom);
|
||||
Rows::iterator new_row = rows_.insert(
|
||||
row_a, Rows::value_type(top, new Row(row_a->second->top, top)));
|
||||
row_a->second->top = top;
|
||||
new_row->second->spans = row_a->second->spans;
|
||||
} else if (top < row_a->second->top) {
|
||||
// If the `top` is above `row_a` then skip the range between `top` and
|
||||
// top of `row_a` because it's empty.
|
||||
top = row_a->second->top;
|
||||
if (top >= row_b->second->bottom) {
|
||||
++row_b;
|
||||
if (row_b != region.rows_.end())
|
||||
top = row_b->second->top;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (row_b->second->bottom < row_a->second->bottom) {
|
||||
// If the bottom of `row_b` falls in the middle of the `row_a` split
|
||||
// `row_a` into two, at `top`, and leave `row_a` referring to the upper of
|
||||
// the two, ready to subtract spans from.
|
||||
int bottom = row_b->second->bottom;
|
||||
Rows::iterator new_row =
|
||||
rows_.insert(row_a, Rows::value_type(bottom, new Row(top, bottom)));
|
||||
row_a->second->top = bottom;
|
||||
new_row->second->spans = row_a->second->spans;
|
||||
row_a = new_row;
|
||||
}
|
||||
|
||||
// At this point the vertical range covered by `row_a` lays within the
|
||||
// range covered by `row_b`. Subtract `row_b` spans from `row_a`.
|
||||
RowSpanSet new_spans;
|
||||
SubtractRows(row_a->second->spans, row_b->second->spans, &new_spans);
|
||||
new_spans.swap(row_a->second->spans);
|
||||
top = row_a->second->bottom;
|
||||
|
||||
if (top >= row_b->second->bottom) {
|
||||
++row_b;
|
||||
if (row_b != region.rows_.end())
|
||||
top = row_b->second->top;
|
||||
}
|
||||
|
||||
// Check if the row is empty after subtraction and delete it. Otherwise move
|
||||
// to the next one.
|
||||
if (row_a->second->spans.empty()) {
|
||||
Rows::iterator row_to_delete = row_a;
|
||||
++row_a;
|
||||
delete row_to_delete->second;
|
||||
rows_.erase(row_to_delete);
|
||||
} else {
|
||||
MergeWithPrecedingRow(row_a);
|
||||
++row_a;
|
||||
}
|
||||
}
|
||||
|
||||
if (row_a != rows_.end())
|
||||
MergeWithPrecedingRow(row_a);
|
||||
}
|
||||
|
||||
void DesktopRegion::Subtract(const DesktopRect& rect) {
|
||||
DesktopRegion region;
|
||||
region.AddRect(rect);
|
||||
Subtract(region);
|
||||
}
|
||||
|
||||
void DesktopRegion::Translate(int32_t dx, int32_t dy) {
|
||||
Rows new_rows;
|
||||
|
||||
for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) {
|
||||
Row* row = it->second;
|
||||
|
||||
row->top += dy;
|
||||
row->bottom += dy;
|
||||
|
||||
if (dx != 0) {
|
||||
// Translate each span.
|
||||
for (RowSpanSet::iterator span = row->spans.begin();
|
||||
span != row->spans.end(); ++span) {
|
||||
span->left += dx;
|
||||
span->right += dx;
|
||||
}
|
||||
}
|
||||
|
||||
if (dy != 0)
|
||||
new_rows.insert(new_rows.end(), Rows::value_type(row->bottom, row));
|
||||
}
|
||||
|
||||
if (dy != 0)
|
||||
new_rows.swap(rows_);
|
||||
}
|
||||
|
||||
void DesktopRegion::Swap(DesktopRegion* region) {
|
||||
rows_.swap(region->rows_);
|
||||
}
|
||||
|
||||
// static
|
||||
bool DesktopRegion::CompareSpanRight(const RowSpan& r, int32_t value) {
|
||||
return r.right < value;
|
||||
}
|
||||
|
||||
// static
|
||||
bool DesktopRegion::CompareSpanLeft(const RowSpan& r, int32_t value) {
|
||||
return r.left < value;
|
||||
}
|
||||
|
||||
// static
|
||||
void DesktopRegion::AddSpanToRow(Row* row, int left, int right) {
|
||||
// First check if the new span is located to the right of all existing spans.
|
||||
// This is an optimization to avoid binary search in the case when rectangles
|
||||
// are inserted sequentially from left to right.
|
||||
if (row->spans.empty() || left > row->spans.back().right) {
|
||||
row->spans.push_back(RowSpan(left, right));
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first span that ends at or after `left`.
|
||||
RowSpanSet::iterator start = std::lower_bound(
|
||||
row->spans.begin(), row->spans.end(), left, CompareSpanRight);
|
||||
RTC_DCHECK(start < row->spans.end());
|
||||
|
||||
// Find the first span that starts after `right`.
|
||||
RowSpanSet::iterator end =
|
||||
std::lower_bound(start, row->spans.end(), right + 1, CompareSpanLeft);
|
||||
if (end == row->spans.begin()) {
|
||||
// There are no overlaps. Just insert the new span at the beginning.
|
||||
row->spans.insert(row->spans.begin(), RowSpan(left, right));
|
||||
return;
|
||||
}
|
||||
|
||||
// Move end to the left, so that it points the last span that ends at or
|
||||
// before `right`.
|
||||
end--;
|
||||
|
||||
// At this point [start, end] is the range of spans that intersect with the
|
||||
// new one.
|
||||
if (end < start) {
|
||||
// There are no overlaps. Just insert the new span at the correct position.
|
||||
row->spans.insert(start, RowSpan(left, right));
|
||||
return;
|
||||
}
|
||||
|
||||
left = std::min(left, start->left);
|
||||
right = std::max(right, end->right);
|
||||
|
||||
// Replace range [start, end] with the new span.
|
||||
*start = RowSpan(left, right);
|
||||
++start;
|
||||
++end;
|
||||
if (start < end)
|
||||
row->spans.erase(start, end);
|
||||
}
|
||||
|
||||
// static
|
||||
bool DesktopRegion::IsSpanInRow(const Row& row, const RowSpan& span) {
|
||||
// Find the first span that starts at or after `span.left` and then check if
|
||||
// it's the same span.
|
||||
RowSpanSet::const_iterator it = std::lower_bound(
|
||||
row.spans.begin(), row.spans.end(), span.left, CompareSpanLeft);
|
||||
return it != row.spans.end() && *it == span;
|
||||
}
|
||||
|
||||
// static
|
||||
void DesktopRegion::SubtractRows(const RowSpanSet& set_a,
|
||||
const RowSpanSet& set_b,
|
||||
RowSpanSet* output) {
|
||||
RTC_DCHECK(!set_a.empty() && !set_b.empty());
|
||||
|
||||
RowSpanSet::const_iterator it_b = set_b.begin();
|
||||
|
||||
// Iterate over all spans in `set_a` adding parts of it that do not intersect
|
||||
// with `set_b` to the `output`.
|
||||
for (RowSpanSet::const_iterator it_a = set_a.begin(); it_a != set_a.end();
|
||||
++it_a) {
|
||||
// If there is no intersection then append the current span and continue.
|
||||
if (it_b == set_b.end() || it_a->right < it_b->left) {
|
||||
output->push_back(*it_a);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over `set_b` spans that may intersect with `it_a`.
|
||||
int pos = it_a->left;
|
||||
while (it_b != set_b.end() && it_b->left < it_a->right) {
|
||||
if (it_b->left > pos)
|
||||
output->push_back(RowSpan(pos, it_b->left));
|
||||
if (it_b->right > pos) {
|
||||
pos = it_b->right;
|
||||
if (pos >= it_a->right)
|
||||
break;
|
||||
}
|
||||
++it_b;
|
||||
}
|
||||
if (pos < it_a->right)
|
||||
output->push_back(RowSpan(pos, it_a->right));
|
||||
}
|
||||
}
|
||||
|
||||
DesktopRegion::Iterator::Iterator(const DesktopRegion& region)
|
||||
: region_(region),
|
||||
row_(region.rows_.begin()),
|
||||
previous_row_(region.rows_.end()) {
|
||||
if (!IsAtEnd()) {
|
||||
RTC_DCHECK_GT(row_->second->spans.size(), 0);
|
||||
row_span_ = row_->second->spans.begin();
|
||||
UpdateCurrentRect();
|
||||
}
|
||||
}
|
||||
|
||||
DesktopRegion::Iterator::~Iterator() {}
|
||||
|
||||
bool DesktopRegion::Iterator::IsAtEnd() const {
|
||||
return row_ == region_.rows_.end();
|
||||
}
|
||||
|
||||
void DesktopRegion::Iterator::Advance() {
|
||||
RTC_DCHECK(!IsAtEnd());
|
||||
|
||||
while (true) {
|
||||
++row_span_;
|
||||
if (row_span_ == row_->second->spans.end()) {
|
||||
previous_row_ = row_;
|
||||
++row_;
|
||||
if (row_ != region_.rows_.end()) {
|
||||
RTC_DCHECK_GT(row_->second->spans.size(), 0);
|
||||
row_span_ = row_->second->spans.begin();
|
||||
}
|
||||
}
|
||||
|
||||
if (IsAtEnd())
|
||||
return;
|
||||
|
||||
// If the same span exists on the previous row then skip it, as we've
|
||||
// already returned this span merged into the previous one, via
|
||||
// UpdateCurrentRect().
|
||||
if (previous_row_ != region_.rows_.end() &&
|
||||
previous_row_->second->bottom == row_->second->top &&
|
||||
IsSpanInRow(*previous_row_->second, *row_span_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
RTC_DCHECK(!IsAtEnd());
|
||||
UpdateCurrentRect();
|
||||
}
|
||||
|
||||
void DesktopRegion::Iterator::UpdateCurrentRect() {
|
||||
// Merge the current rectangle with the matching spans from later rows.
|
||||
int bottom;
|
||||
Rows::const_iterator bottom_row = row_;
|
||||
Rows::const_iterator previous;
|
||||
do {
|
||||
bottom = bottom_row->second->bottom;
|
||||
previous = bottom_row;
|
||||
++bottom_row;
|
||||
} while (bottom_row != region_.rows_.end() &&
|
||||
previous->second->bottom == bottom_row->second->top &&
|
||||
IsSpanInRow(*bottom_row->second, *row_span_));
|
||||
rect_ = DesktopRect::MakeLTRB(row_span_->left, row_->second->top,
|
||||
row_span_->right, bottom);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// DesktopRegion represents a region of the screen or window.
|
||||
//
|
||||
// Internally each region is stored as a set of rows where each row contains one
|
||||
// or more rectangles aligned vertically.
|
||||
class RTC_EXPORT DesktopRegion {
|
||||
private:
|
||||
// The following private types need to be declared first because they are used
|
||||
// in the public Iterator.
|
||||
|
||||
// RowSpan represents a horizontal span withing a single row.
|
||||
struct RowSpan {
|
||||
RowSpan(int32_t left, int32_t right);
|
||||
|
||||
// Used by std::vector<>.
|
||||
bool operator==(const RowSpan& that) const {
|
||||
return left == that.left && right == that.right;
|
||||
}
|
||||
|
||||
int32_t left;
|
||||
int32_t right;
|
||||
};
|
||||
|
||||
typedef std::vector<RowSpan> RowSpanSet;
|
||||
|
||||
// Row represents a single row of a region. A row is set of rectangles that
|
||||
// have the same vertical position.
|
||||
struct Row {
|
||||
Row(const Row&);
|
||||
Row(Row&&);
|
||||
Row(int32_t top, int32_t bottom);
|
||||
~Row();
|
||||
|
||||
int32_t top;
|
||||
int32_t bottom;
|
||||
|
||||
RowSpanSet spans;
|
||||
};
|
||||
|
||||
// Type used to store list of rows in the region. The bottom position of row
|
||||
// is used as the key so that rows are always ordered by their position. The
|
||||
// map stores pointers to make Translate() more efficient.
|
||||
typedef std::map<int, Row*> Rows;
|
||||
|
||||
public:
|
||||
// Iterator that can be used to iterate over rectangles of a DesktopRegion.
|
||||
// The region must not be mutated while the iterator is used.
|
||||
class RTC_EXPORT Iterator {
|
||||
public:
|
||||
explicit Iterator(const DesktopRegion& target);
|
||||
~Iterator();
|
||||
|
||||
bool IsAtEnd() const;
|
||||
void Advance();
|
||||
|
||||
const DesktopRect& rect() const { return rect_; }
|
||||
|
||||
private:
|
||||
const DesktopRegion& region_;
|
||||
|
||||
// Updates `rect_` based on the current `row_` and `row_span_`. If
|
||||
// `row_span_` matches spans on consecutive rows then they are also merged
|
||||
// into `rect_`, to generate more efficient output.
|
||||
void UpdateCurrentRect();
|
||||
|
||||
Rows::const_iterator row_;
|
||||
Rows::const_iterator previous_row_;
|
||||
RowSpanSet::const_iterator row_span_;
|
||||
DesktopRect rect_;
|
||||
};
|
||||
|
||||
DesktopRegion();
|
||||
explicit DesktopRegion(const DesktopRect& rect);
|
||||
DesktopRegion(const DesktopRect* rects, int count);
|
||||
DesktopRegion(const DesktopRegion& other);
|
||||
~DesktopRegion();
|
||||
|
||||
DesktopRegion& operator=(const DesktopRegion& other);
|
||||
|
||||
bool is_empty() const { return rows_.empty(); }
|
||||
|
||||
bool Equals(const DesktopRegion& region) const;
|
||||
|
||||
// Reset the region to be empty.
|
||||
void Clear();
|
||||
|
||||
// Reset region to contain just `rect`.
|
||||
void SetRect(const DesktopRect& rect);
|
||||
|
||||
// Adds specified rect(s) or region to the region.
|
||||
void AddRect(const DesktopRect& rect);
|
||||
void AddRects(const DesktopRect* rects, int count);
|
||||
void AddRegion(const DesktopRegion& region);
|
||||
|
||||
// Finds intersection of two regions and stores them in the current region.
|
||||
void Intersect(const DesktopRegion& region1, const DesktopRegion& region2);
|
||||
|
||||
// Same as above but intersects content of the current region with `region`.
|
||||
void IntersectWith(const DesktopRegion& region);
|
||||
|
||||
// Clips the region by the `rect`.
|
||||
void IntersectWith(const DesktopRect& rect);
|
||||
|
||||
// Subtracts `region` from the current content of the region.
|
||||
void Subtract(const DesktopRegion& region);
|
||||
|
||||
// Subtracts `rect` from the current content of the region.
|
||||
void Subtract(const DesktopRect& rect);
|
||||
|
||||
// Adds (dx, dy) to the position of the region.
|
||||
void Translate(int32_t dx, int32_t dy);
|
||||
|
||||
void Swap(DesktopRegion* region);
|
||||
|
||||
private:
|
||||
// Comparison functions used for std::lower_bound(). Compare left or right
|
||||
// edges withs a given `value`.
|
||||
static bool CompareSpanLeft(const RowSpan& r, int32_t value);
|
||||
static bool CompareSpanRight(const RowSpan& r, int32_t value);
|
||||
|
||||
// Adds a new span to the row, coalescing spans if necessary.
|
||||
static void AddSpanToRow(Row* row, int32_t left, int32_t right);
|
||||
|
||||
// Returns true if the `span` exists in the given `row`.
|
||||
static bool IsSpanInRow(const Row& row, const RowSpan& rect);
|
||||
|
||||
// Calculates the intersection of two sets of spans.
|
||||
static void IntersectRows(const RowSpanSet& set1,
|
||||
const RowSpanSet& set2,
|
||||
RowSpanSet* output);
|
||||
|
||||
static void SubtractRows(const RowSpanSet& set_a,
|
||||
const RowSpanSet& set_b,
|
||||
RowSpanSet* output);
|
||||
|
||||
// Merges `row` with the row above it if they contain the same spans. Doesn't
|
||||
// do anything if called with `row` set to rows_.begin() (i.e. first row of
|
||||
// the region). If the rows were merged `row` remains a valid iterator to the
|
||||
// merged row.
|
||||
void MergeWithPrecedingRow(Rows::iterator row);
|
||||
|
||||
Rows rows_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/differ_block.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "rtc_base/system/arch.h"
|
||||
#include "system_wrappers/include/cpu_features_wrapper.h"
|
||||
|
||||
// This needs to be after rtc_base/system/arch.h which defines
|
||||
// architecture macros.
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
#include "modules/desktop_capture/differ_vector_sse2.h"
|
||||
#endif
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
bool VectorDifference_C(const uint8_t* image1, const uint8_t* image2) {
|
||||
return memcmp(image1, image2, kBlockSize * kBytesPerPixel) != 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool VectorDifference(const uint8_t* image1, const uint8_t* image2) {
|
||||
static bool (*diff_proc)(const uint8_t*, const uint8_t*) = nullptr;
|
||||
|
||||
if (!diff_proc) {
|
||||
#if defined(WEBRTC_ARCH_X86_FAMILY)
|
||||
bool have_sse2 = GetCPUInfo(kSSE2) != 0;
|
||||
// For x86 processors, check if SSE2 is supported.
|
||||
if (have_sse2 && kBlockSize == 32) {
|
||||
diff_proc = &VectorDifference_SSE2_W32;
|
||||
} else if (have_sse2 && kBlockSize == 16) {
|
||||
diff_proc = &VectorDifference_SSE2_W16;
|
||||
} else {
|
||||
diff_proc = &VectorDifference_C;
|
||||
}
|
||||
#else
|
||||
// For other processors, always use C version.
|
||||
// TODO(hclam): Implement a NEON version.
|
||||
diff_proc = &VectorDifference_C;
|
||||
#endif
|
||||
}
|
||||
|
||||
return diff_proc(image1, image2);
|
||||
}
|
||||
|
||||
bool BlockDifference(const uint8_t* image1,
|
||||
const uint8_t* image2,
|
||||
int height,
|
||||
int stride) {
|
||||
for (int i = 0; i < height; i++) {
|
||||
if (VectorDifference(image1, image2)) {
|
||||
return true;
|
||||
}
|
||||
image1 += stride;
|
||||
image2 += stride;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BlockDifference(const uint8_t* image1, const uint8_t* image2, int stride) {
|
||||
return BlockDifference(image1, image2, kBlockSize, stride);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Size (in pixels) of each square block used for diffing. This must be a
|
||||
// multiple of sizeof(uint64)/8.
|
||||
const int kBlockSize = 32;
|
||||
|
||||
// Format: BGRA 32 bit.
|
||||
const int kBytesPerPixel = 4;
|
||||
|
||||
// Low level function to compare 2 vectors of pixels of size kBlockSize. Returns
|
||||
// whether the blocks differ.
|
||||
bool VectorDifference(const uint8_t* image1, const uint8_t* image2);
|
||||
|
||||
// Low level function to compare 2 blocks of pixels of size
|
||||
// (kBlockSize, `height`). Returns whether the blocks differ.
|
||||
bool BlockDifference(const uint8_t* image1,
|
||||
const uint8_t* image2,
|
||||
int height,
|
||||
int stride);
|
||||
|
||||
// Low level function to compare 2 blocks of pixels of size
|
||||
// (kBlockSize, kBlockSize). Returns whether the blocks differ.
|
||||
bool BlockDifference(const uint8_t* image1, const uint8_t* image2, int stride);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DIFFER_BLOCK_H_
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/differ_vector_sse2.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <emmintrin.h>
|
||||
#include <mmintrin.h>
|
||||
#endif
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
extern bool VectorDifference_SSE2_W16(const uint8_t* image1,
|
||||
const uint8_t* image2) {
|
||||
__m128i acc = _mm_setzero_si128();
|
||||
__m128i v0;
|
||||
__m128i v1;
|
||||
__m128i sad;
|
||||
const __m128i* i1 = reinterpret_cast<const __m128i*>(image1);
|
||||
const __m128i* i2 = reinterpret_cast<const __m128i*>(image2);
|
||||
v0 = _mm_loadu_si128(i1);
|
||||
v1 = _mm_loadu_si128(i2);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 1);
|
||||
v1 = _mm_loadu_si128(i2 + 1);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 2);
|
||||
v1 = _mm_loadu_si128(i2 + 2);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 3);
|
||||
v1 = _mm_loadu_si128(i2 + 3);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
|
||||
// This essential means sad = acc >> 64. We only care about the lower 16
|
||||
// bits.
|
||||
sad = _mm_shuffle_epi32(acc, 0xEE);
|
||||
sad = _mm_adds_epu16(sad, acc);
|
||||
return _mm_cvtsi128_si32(sad) != 0;
|
||||
}
|
||||
|
||||
extern bool VectorDifference_SSE2_W32(const uint8_t* image1,
|
||||
const uint8_t* image2) {
|
||||
__m128i acc = _mm_setzero_si128();
|
||||
__m128i v0;
|
||||
__m128i v1;
|
||||
__m128i sad;
|
||||
const __m128i* i1 = reinterpret_cast<const __m128i*>(image1);
|
||||
const __m128i* i2 = reinterpret_cast<const __m128i*>(image2);
|
||||
v0 = _mm_loadu_si128(i1);
|
||||
v1 = _mm_loadu_si128(i2);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 1);
|
||||
v1 = _mm_loadu_si128(i2 + 1);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 2);
|
||||
v1 = _mm_loadu_si128(i2 + 2);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 3);
|
||||
v1 = _mm_loadu_si128(i2 + 3);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 4);
|
||||
v1 = _mm_loadu_si128(i2 + 4);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 5);
|
||||
v1 = _mm_loadu_si128(i2 + 5);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 6);
|
||||
v1 = _mm_loadu_si128(i2 + 6);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
v0 = _mm_loadu_si128(i1 + 7);
|
||||
v1 = _mm_loadu_si128(i2 + 7);
|
||||
sad = _mm_sad_epu8(v0, v1);
|
||||
acc = _mm_adds_epu16(acc, sad);
|
||||
|
||||
// This essential means sad = acc >> 64. We only care about the lower 16
|
||||
// bits.
|
||||
sad = _mm_shuffle_epi32(acc, 0xEE);
|
||||
sad = _mm_adds_epu16(sad, acc);
|
||||
return _mm_cvtsi128_si32(sad) != 0;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// This header file is used only differ_block.h. It defines the SSE2 rountines
|
||||
// for finding vector difference.
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_DIFFER_VECTOR_SSE2_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_DIFFER_VECTOR_SSE2_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Find vector difference of dimension 16.
|
||||
extern bool VectorDifference_SSE2_W16(const uint8_t* image1,
|
||||
const uint8_t* image2);
|
||||
|
||||
// Find vector difference of dimension 32.
|
||||
extern bool VectorDifference_SSE2_W32(const uint8_t* image1,
|
||||
const uint8_t* image2);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_DIFFER_VECTOR_SSE2_H_
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/fake_desktop_capturer.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
FakeDesktopCapturer::FakeDesktopCapturer() = default;
|
||||
FakeDesktopCapturer::~FakeDesktopCapturer() = default;
|
||||
|
||||
void FakeDesktopCapturer::set_result(DesktopCapturer::Result result) {
|
||||
result_ = result;
|
||||
}
|
||||
|
||||
int FakeDesktopCapturer::num_frames_captured() const {
|
||||
return num_frames_captured_;
|
||||
}
|
||||
|
||||
int FakeDesktopCapturer::num_capture_attempts() const {
|
||||
return num_capture_attempts_;
|
||||
}
|
||||
|
||||
// Uses the `generator` provided as DesktopFrameGenerator, FakeDesktopCapturer
|
||||
// does
|
||||
// not take the ownership of `generator`.
|
||||
void FakeDesktopCapturer::set_frame_generator(
|
||||
DesktopFrameGenerator* generator) {
|
||||
generator_ = generator;
|
||||
}
|
||||
|
||||
void FakeDesktopCapturer::Start(DesktopCapturer::Callback* callback) {
|
||||
callback_ = callback;
|
||||
}
|
||||
|
||||
void FakeDesktopCapturer::CaptureFrame() {
|
||||
num_capture_attempts_++;
|
||||
if (generator_) {
|
||||
if (result_ != DesktopCapturer::Result::SUCCESS) {
|
||||
callback_->OnCaptureResult(result_, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> frame(
|
||||
generator_->GetNextFrame(shared_memory_factory_.get()));
|
||||
if (frame) {
|
||||
num_frames_captured_++;
|
||||
callback_->OnCaptureResult(result_, std::move(frame));
|
||||
} else {
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_TEMPORARY,
|
||||
nullptr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
callback_->OnCaptureResult(DesktopCapturer::Result::ERROR_PERMANENT, nullptr);
|
||||
}
|
||||
|
||||
void FakeDesktopCapturer::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
shared_memory_factory_ = std::move(shared_memory_factory);
|
||||
}
|
||||
|
||||
bool FakeDesktopCapturer::GetSourceList(DesktopCapturer::SourceList* sources) {
|
||||
sources->push_back({kWindowId, "A-Fake-DesktopCapturer-Window"});
|
||||
sources->push_back({kScreenId});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FakeDesktopCapturer::SelectSource(DesktopCapturer::SourceId id) {
|
||||
return id == kWindowId || id == kScreenId || id == kFullDesktopScreenId;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_FAKE_DESKTOP_CAPTURER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_FAKE_DESKTOP_CAPTURER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame_generator.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A fake implementation of DesktopCapturer or its derived interfaces to
|
||||
// generate DesktopFrame for testing purpose.
|
||||
//
|
||||
// Consumers can provide a FrameGenerator instance to generate instances of
|
||||
// DesktopFrame to return for each Capture() function call.
|
||||
// If no FrameGenerator provided, FakeDesktopCapturer will always return a
|
||||
// nullptr DesktopFrame.
|
||||
//
|
||||
// Double buffering is guaranteed by the FrameGenerator. FrameGenerator
|
||||
// implements in desktop_frame_generator.h guarantee double buffering, they
|
||||
// creates a new instance of DesktopFrame each time.
|
||||
class RTC_EXPORT FakeDesktopCapturer : public DesktopCapturer {
|
||||
public:
|
||||
FakeDesktopCapturer();
|
||||
~FakeDesktopCapturer() override;
|
||||
|
||||
// Decides the result which will be returned in next Capture() callback.
|
||||
void set_result(DesktopCapturer::Result result);
|
||||
|
||||
// Uses the `generator` provided as DesktopFrameGenerator, FakeDesktopCapturer
|
||||
// does not take the ownership of `generator`.
|
||||
void set_frame_generator(DesktopFrameGenerator* generator);
|
||||
|
||||
// Count of DesktopFrame(s) have been returned by this instance. This field
|
||||
// would never be negative.
|
||||
int num_frames_captured() const;
|
||||
|
||||
// Count of CaptureFrame() calls have been made. This field would never be
|
||||
// negative.
|
||||
int num_capture_attempts() const;
|
||||
|
||||
// DesktopCapturer interface
|
||||
void Start(DesktopCapturer::Callback* callback) override;
|
||||
void CaptureFrame() override;
|
||||
void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
||||
bool GetSourceList(DesktopCapturer::SourceList* sources) override;
|
||||
bool SelectSource(DesktopCapturer::SourceId id) override;
|
||||
|
||||
private:
|
||||
static constexpr DesktopCapturer::SourceId kWindowId = 1378277495;
|
||||
static constexpr DesktopCapturer::SourceId kScreenId = 1378277496;
|
||||
|
||||
DesktopCapturer::Callback* callback_ = nullptr;
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory_;
|
||||
DesktopCapturer::Result result_ = Result::SUCCESS;
|
||||
DesktopFrameGenerator* generator_ = nullptr;
|
||||
int num_frames_captured_ = 0;
|
||||
int num_capture_attempts_ = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_FAKE_DESKTOP_CAPTURER_H_
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/fallback_desktop_capturer_wrapper.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "api/sequence_checker.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "system_wrappers/include/metrics.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Implementation to share a SharedMemoryFactory between DesktopCapturer
|
||||
// instances. This class is designed for synchronized DesktopCapturer
|
||||
// implementations only.
|
||||
class SharedMemoryFactoryProxy : public SharedMemoryFactory {
|
||||
public:
|
||||
// Users should maintain the lifetime of `factory` to ensure it overlives
|
||||
// current instance.
|
||||
static std::unique_ptr<SharedMemoryFactory> Create(
|
||||
SharedMemoryFactory* factory);
|
||||
~SharedMemoryFactoryProxy() override;
|
||||
|
||||
// Forwards CreateSharedMemory() calls to `factory_`. Users should always call
|
||||
// this function in one thread. Users should not call this function after the
|
||||
// SharedMemoryFactory which current instance created from has been destroyed.
|
||||
std::unique_ptr<SharedMemory> CreateSharedMemory(size_t size) override;
|
||||
|
||||
private:
|
||||
explicit SharedMemoryFactoryProxy(SharedMemoryFactory* factory);
|
||||
|
||||
SharedMemoryFactory* factory_ = nullptr;
|
||||
SequenceChecker thread_checker_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
SharedMemoryFactoryProxy::SharedMemoryFactoryProxy(
|
||||
SharedMemoryFactory* factory) {
|
||||
RTC_DCHECK(factory);
|
||||
factory_ = factory;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<SharedMemoryFactory> SharedMemoryFactoryProxy::Create(
|
||||
SharedMemoryFactory* factory) {
|
||||
return std::unique_ptr<SharedMemoryFactory>(
|
||||
new SharedMemoryFactoryProxy(factory));
|
||||
}
|
||||
|
||||
SharedMemoryFactoryProxy::~SharedMemoryFactoryProxy() = default;
|
||||
|
||||
std::unique_ptr<SharedMemory> SharedMemoryFactoryProxy::CreateSharedMemory(
|
||||
size_t size) {
|
||||
RTC_DCHECK(thread_checker_.IsCurrent());
|
||||
return factory_->CreateSharedMemory(size);
|
||||
}
|
||||
|
||||
FallbackDesktopCapturerWrapper::FallbackDesktopCapturerWrapper(
|
||||
std::unique_ptr<DesktopCapturer> main_capturer,
|
||||
std::unique_ptr<DesktopCapturer> secondary_capturer)
|
||||
: main_capturer_(std::move(main_capturer)),
|
||||
secondary_capturer_(std::move(secondary_capturer)) {
|
||||
RTC_DCHECK(main_capturer_);
|
||||
RTC_DCHECK(secondary_capturer_);
|
||||
}
|
||||
|
||||
FallbackDesktopCapturerWrapper::~FallbackDesktopCapturerWrapper() = default;
|
||||
|
||||
void FallbackDesktopCapturerWrapper::Start(
|
||||
DesktopCapturer::Callback* callback) {
|
||||
callback_ = callback;
|
||||
// FallbackDesktopCapturerWrapper catchs the callback of the main capturer,
|
||||
// and checks its return value to decide whether the secondary capturer should
|
||||
// be involved.
|
||||
main_capturer_->Start(this);
|
||||
// For the secondary capturer, we do not have a backup plan anymore, so
|
||||
// FallbackDesktopCapturerWrapper won't check its return value any more. It
|
||||
// will directly return to the input `callback`.
|
||||
secondary_capturer_->Start(callback);
|
||||
}
|
||||
|
||||
void FallbackDesktopCapturerWrapper::SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
|
||||
shared_memory_factory_ = std::move(shared_memory_factory);
|
||||
if (shared_memory_factory_) {
|
||||
main_capturer_->SetSharedMemoryFactory(
|
||||
SharedMemoryFactoryProxy::Create(shared_memory_factory_.get()));
|
||||
secondary_capturer_->SetSharedMemoryFactory(
|
||||
SharedMemoryFactoryProxy::Create(shared_memory_factory_.get()));
|
||||
} else {
|
||||
main_capturer_->SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory>());
|
||||
secondary_capturer_->SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory>());
|
||||
}
|
||||
}
|
||||
|
||||
void FallbackDesktopCapturerWrapper::CaptureFrame() {
|
||||
RTC_DCHECK(callback_);
|
||||
if (main_capturer_permanent_error_) {
|
||||
secondary_capturer_->CaptureFrame();
|
||||
} else {
|
||||
main_capturer_->CaptureFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void FallbackDesktopCapturerWrapper::SetExcludedWindow(WindowId window) {
|
||||
main_capturer_->SetExcludedWindow(window);
|
||||
secondary_capturer_->SetExcludedWindow(window);
|
||||
}
|
||||
|
||||
bool FallbackDesktopCapturerWrapper::GetSourceList(SourceList* sources) {
|
||||
if (main_capturer_permanent_error_) {
|
||||
return secondary_capturer_->GetSourceList(sources);
|
||||
}
|
||||
return main_capturer_->GetSourceList(sources);
|
||||
}
|
||||
|
||||
bool FallbackDesktopCapturerWrapper::SelectSource(SourceId id) {
|
||||
if (main_capturer_permanent_error_) {
|
||||
return secondary_capturer_->SelectSource(id);
|
||||
}
|
||||
const bool main_capturer_result = main_capturer_->SelectSource(id);
|
||||
RTC_HISTOGRAM_BOOLEAN(
|
||||
"WebRTC.DesktopCapture.PrimaryCapturerSelectSourceError",
|
||||
main_capturer_result);
|
||||
if (!main_capturer_result) {
|
||||
main_capturer_permanent_error_ = true;
|
||||
}
|
||||
|
||||
return secondary_capturer_->SelectSource(id);
|
||||
}
|
||||
|
||||
bool FallbackDesktopCapturerWrapper::FocusOnSelectedSource() {
|
||||
if (main_capturer_permanent_error_) {
|
||||
return secondary_capturer_->FocusOnSelectedSource();
|
||||
}
|
||||
return main_capturer_->FocusOnSelectedSource() ||
|
||||
secondary_capturer_->FocusOnSelectedSource();
|
||||
}
|
||||
|
||||
bool FallbackDesktopCapturerWrapper::IsOccluded(const DesktopVector& pos) {
|
||||
// Returns true if either capturer returns true.
|
||||
if (main_capturer_permanent_error_) {
|
||||
return secondary_capturer_->IsOccluded(pos);
|
||||
}
|
||||
return main_capturer_->IsOccluded(pos) ||
|
||||
secondary_capturer_->IsOccluded(pos);
|
||||
}
|
||||
|
||||
void FallbackDesktopCapturerWrapper::OnCaptureResult(
|
||||
Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) {
|
||||
RTC_DCHECK(callback_);
|
||||
RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.PrimaryCapturerError",
|
||||
result != Result::SUCCESS);
|
||||
RTC_HISTOGRAM_BOOLEAN("WebRTC.DesktopCapture.PrimaryCapturerPermanentError",
|
||||
result == Result::ERROR_PERMANENT);
|
||||
if (result == Result::SUCCESS) {
|
||||
callback_->OnCaptureResult(result, std::move(frame));
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == Result::ERROR_PERMANENT) {
|
||||
main_capturer_permanent_error_ = true;
|
||||
}
|
||||
secondary_capturer_->CaptureFrame();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/shared_memory.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A DesktopCapturer wrapper owns two DesktopCapturer implementations. If the
|
||||
// main DesktopCapturer fails, it uses the secondary one instead. Two capturers
|
||||
// are expected to return same SourceList, and the meaning of each SourceId is
|
||||
// identical, otherwise FallbackDesktopCapturerWrapper may return frames from
|
||||
// different sources. Using asynchronized DesktopCapturer implementations with
|
||||
// SharedMemoryFactory is not supported, and may result crash or assertion
|
||||
// failure.
|
||||
class FallbackDesktopCapturerWrapper final : public DesktopCapturer,
|
||||
public DesktopCapturer::Callback {
|
||||
public:
|
||||
FallbackDesktopCapturerWrapper(
|
||||
std::unique_ptr<DesktopCapturer> main_capturer,
|
||||
std::unique_ptr<DesktopCapturer> secondary_capturer);
|
||||
~FallbackDesktopCapturerWrapper() override;
|
||||
|
||||
// DesktopCapturer interface.
|
||||
void Start(DesktopCapturer::Callback* callback) override;
|
||||
void SetSharedMemoryFactory(
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
||||
void CaptureFrame() override;
|
||||
void SetExcludedWindow(WindowId window) override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
bool FocusOnSelectedSource() override;
|
||||
bool IsOccluded(const DesktopVector& pos) override;
|
||||
|
||||
private:
|
||||
// DesktopCapturer::Callback interface.
|
||||
void OnCaptureResult(Result result,
|
||||
std::unique_ptr<DesktopFrame> frame) override;
|
||||
|
||||
const std::unique_ptr<DesktopCapturer> main_capturer_;
|
||||
const std::unique_ptr<DesktopCapturer> secondary_capturer_;
|
||||
std::unique_ptr<SharedMemoryFactory> shared_memory_factory_;
|
||||
bool main_capturer_permanent_error_ = false;
|
||||
DesktopCapturer::Callback* callback_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_FALLBACK_DESKTOP_CAPTURER_WRAPPER_H_
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/full_screen_application_handler.h"
|
||||
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
FullScreenApplicationHandler::FullScreenApplicationHandler(
|
||||
DesktopCapturer::SourceId sourceId)
|
||||
: source_id_(sourceId) {}
|
||||
|
||||
DesktopCapturer::SourceId FullScreenApplicationHandler::FindFullScreenWindow(
|
||||
const DesktopCapturer::SourceList&,
|
||||
int64_t) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
DesktopCapturer::SourceId FullScreenApplicationHandler::GetSourceId() const {
|
||||
return source_id_;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_FULL_SCREEN_APPLICATION_HANDLER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_FULL_SCREEN_APPLICATION_HANDLER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Base class for application specific handler to check criteria for switch to
|
||||
// full-screen mode and find if possible the full-screen window to share.
|
||||
// Supposed to be created and owned by platform specific
|
||||
// FullScreenWindowDetector.
|
||||
class FullScreenApplicationHandler {
|
||||
public:
|
||||
virtual ~FullScreenApplicationHandler() {}
|
||||
|
||||
FullScreenApplicationHandler(const FullScreenApplicationHandler&) = delete;
|
||||
FullScreenApplicationHandler& operator=(const FullScreenApplicationHandler&) =
|
||||
delete;
|
||||
|
||||
explicit FullScreenApplicationHandler(DesktopCapturer::SourceId sourceId);
|
||||
|
||||
// Returns the full-screen window in place of the original window if all the
|
||||
// criteria are met, or 0 if no such window found.
|
||||
virtual DesktopCapturer::SourceId FindFullScreenWindow(
|
||||
const DesktopCapturer::SourceList& window_list,
|
||||
int64_t timestamp) const;
|
||||
|
||||
// Returns source id of original window associated with
|
||||
// FullScreenApplicationHandler
|
||||
DesktopCapturer::SourceId GetSourceId() const;
|
||||
|
||||
private:
|
||||
const DesktopCapturer::SourceId source_id_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_FULL_SCREEN_APPLICATION_HANDLER_H_
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/full_screen_window_detector.h"
|
||||
|
||||
#include "modules/desktop_capture/full_screen_application_handler.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
FullScreenWindowDetector::FullScreenWindowDetector(
|
||||
ApplicationHandlerFactory application_handler_factory)
|
||||
: application_handler_factory_(application_handler_factory),
|
||||
last_update_time_ms_(0),
|
||||
previous_source_id_(0),
|
||||
no_handler_source_id_(0) {}
|
||||
|
||||
DesktopCapturer::SourceId FullScreenWindowDetector::FindFullScreenWindow(
|
||||
DesktopCapturer::SourceId original_source_id) {
|
||||
if (app_handler_ == nullptr ||
|
||||
app_handler_->GetSourceId() != original_source_id) {
|
||||
return 0;
|
||||
}
|
||||
return app_handler_->FindFullScreenWindow(window_list_, last_update_time_ms_);
|
||||
}
|
||||
|
||||
void FullScreenWindowDetector::UpdateWindowListIfNeeded(
|
||||
DesktopCapturer::SourceId original_source_id,
|
||||
rtc::FunctionView<bool(DesktopCapturer::SourceList*)> get_sources) {
|
||||
const bool skip_update = previous_source_id_ != original_source_id;
|
||||
previous_source_id_ = original_source_id;
|
||||
|
||||
// Here is an attempt to avoid redundant creating application handler in case
|
||||
// when an instance of WindowCapturer is used to generate a thumbnail to show
|
||||
// in picker by calling SelectSource and CaptureFrame for every available
|
||||
// source.
|
||||
if (skip_update) {
|
||||
return;
|
||||
}
|
||||
|
||||
CreateApplicationHandlerIfNeeded(original_source_id);
|
||||
if (app_handler_ == nullptr) {
|
||||
// There is no FullScreenApplicationHandler specific for
|
||||
// current application
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int64_t kUpdateIntervalMs = 500;
|
||||
|
||||
if ((rtc::TimeMillis() - last_update_time_ms_) <= kUpdateIntervalMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
DesktopCapturer::SourceList window_list;
|
||||
if (get_sources(&window_list)) {
|
||||
last_update_time_ms_ = rtc::TimeMillis();
|
||||
window_list_.swap(window_list);
|
||||
}
|
||||
}
|
||||
|
||||
void FullScreenWindowDetector::CreateApplicationHandlerIfNeeded(
|
||||
DesktopCapturer::SourceId source_id) {
|
||||
if (no_handler_source_id_ == source_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (app_handler_ == nullptr || app_handler_->GetSourceId() != source_id) {
|
||||
app_handler_ = application_handler_factory_
|
||||
? application_handler_factory_(source_id)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
if (app_handler_ == nullptr) {
|
||||
no_handler_source_id_ = source_id;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_FULL_SCREEN_WINDOW_DETECTOR_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_FULL_SCREEN_WINDOW_DETECTOR_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/function_view.h"
|
||||
#include "api/ref_counted_base.h"
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/full_screen_application_handler.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// This is a way to handle switch to full-screen mode for application in some
|
||||
// specific cases:
|
||||
// - Chrome on MacOS creates a new window in full-screen mode to
|
||||
// show a tab full-screen and minimizes the old window.
|
||||
// - PowerPoint creates new windows in full-screen mode when user goes to
|
||||
// presentation mode (Slide Show Window, Presentation Window).
|
||||
//
|
||||
// To continue capturing in these cases, we try to find the new full-screen
|
||||
// window using criteria provided by application specific
|
||||
// FullScreenApplicationHandler.
|
||||
|
||||
class FullScreenWindowDetector
|
||||
: public rtc::RefCountedNonVirtual<FullScreenWindowDetector> {
|
||||
public:
|
||||
using ApplicationHandlerFactory =
|
||||
std::function<std::unique_ptr<FullScreenApplicationHandler>(
|
||||
DesktopCapturer::SourceId sourceId)>;
|
||||
|
||||
FullScreenWindowDetector(
|
||||
ApplicationHandlerFactory application_handler_factory);
|
||||
|
||||
FullScreenWindowDetector(const FullScreenWindowDetector&) = delete;
|
||||
FullScreenWindowDetector& operator=(const FullScreenWindowDetector&) = delete;
|
||||
|
||||
// Returns the full-screen window in place of the original window if all the
|
||||
// criteria provided by FullScreenApplicationHandler are met, or 0 if no such
|
||||
// window found.
|
||||
DesktopCapturer::SourceId FindFullScreenWindow(
|
||||
DesktopCapturer::SourceId original_source_id);
|
||||
|
||||
// The caller should call this function periodically, implementation will
|
||||
// update internal state no often than twice per second
|
||||
void UpdateWindowListIfNeeded(
|
||||
DesktopCapturer::SourceId original_source_id,
|
||||
rtc::FunctionView<bool(DesktopCapturer::SourceList*)> get_sources);
|
||||
|
||||
static rtc::scoped_refptr<FullScreenWindowDetector>
|
||||
CreateFullScreenWindowDetector();
|
||||
|
||||
protected:
|
||||
std::unique_ptr<FullScreenApplicationHandler> app_handler_;
|
||||
|
||||
private:
|
||||
void CreateApplicationHandlerIfNeeded(DesktopCapturer::SourceId source_id);
|
||||
|
||||
ApplicationHandlerFactory application_handler_factory_;
|
||||
|
||||
int64_t last_update_time_ms_;
|
||||
DesktopCapturer::SourceId previous_source_id_;
|
||||
|
||||
// Save the source id when we fail to create an instance of
|
||||
// CreateApplicationHandlerIfNeeded to avoid redundant attempt to do it again.
|
||||
DesktopCapturer::SourceId no_handler_source_id_;
|
||||
|
||||
DesktopCapturer::SourceList window_list_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_FULL_SCREEN_WINDOW_DETECTOR_H_
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/wayland/base_capturer_pipewire.h"
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/linux/wayland/restore_token_manager.h"
|
||||
#include "modules/portal/pipewire_utils.h"
|
||||
#include "modules/portal/xdg_desktop_portal_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "rtc_base/trace_event.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
using xdg_portal::RequestResponse;
|
||||
using xdg_portal::ScreenCapturePortalInterface;
|
||||
using xdg_portal::SessionDetails;
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
bool BaseCapturerPipeWire::IsSupported() {
|
||||
// Unfortunately, the best way we have to check if PipeWire is available is
|
||||
// to try to initialize it.
|
||||
// InitializePipeWire should prevent us from repeatedly initializing PipeWire,
|
||||
// but we also don't really expect support to change without the application
|
||||
// restarting.
|
||||
static bool supported =
|
||||
DesktopCapturer::IsRunningUnderWayland() && InitializePipeWire();
|
||||
return supported;
|
||||
}
|
||||
|
||||
BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options,
|
||||
CaptureType type)
|
||||
: BaseCapturerPipeWire(options,
|
||||
std::make_unique<ScreenCastPortal>(type, this)) {
|
||||
is_screencast_portal_ = true;
|
||||
}
|
||||
|
||||
BaseCapturerPipeWire::BaseCapturerPipeWire(
|
||||
const DesktopCaptureOptions& options,
|
||||
std::unique_ptr<ScreenCapturePortalInterface> portal)
|
||||
: options_(options),
|
||||
is_screencast_portal_(false),
|
||||
portal_(std::move(portal)) {
|
||||
source_id_ = RestoreTokenManager::GetInstance().GetUnusedId();
|
||||
options_.screencast_stream()->SetUseDamageRegion(
|
||||
options_.pipewire_use_damage_region());
|
||||
}
|
||||
|
||||
BaseCapturerPipeWire::~BaseCapturerPipeWire() {
|
||||
options_.screencast_stream()->StopScreenCastStream();
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result,
|
||||
uint32_t stream_node_id,
|
||||
int fd) {
|
||||
is_portal_open_ = false;
|
||||
|
||||
// Reset the value of capturer_failed_ in case we succeed below. If we fail,
|
||||
// then it'll set it to the right value again soon enough.
|
||||
capturer_failed_ = false;
|
||||
if (result != RequestResponse::kSuccess ||
|
||||
!options_.screencast_stream()->StartScreenCastStream(
|
||||
stream_node_id, fd, options_.get_width(), options_.get_height(),
|
||||
options_.prefer_cursor_embedded(),
|
||||
send_frames_immediately_ ? callback_ : nullptr)) {
|
||||
capturer_failed_ = true;
|
||||
RTC_LOG(LS_ERROR) << "ScreenCastPortal failed: "
|
||||
<< static_cast<uint>(result);
|
||||
} else if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
|
||||
if (!screencast_portal->RestoreToken().empty()) {
|
||||
const SourceId token_id =
|
||||
selected_source_id_ ? selected_source_id_ : source_id_;
|
||||
RestoreTokenManager::GetInstance().AddToken(
|
||||
token_id, screencast_portal->RestoreToken());
|
||||
}
|
||||
}
|
||||
|
||||
if (!delegated_source_list_observer_)
|
||||
return;
|
||||
|
||||
switch (result) {
|
||||
case RequestResponse::kUnknown:
|
||||
RTC_DCHECK_NOTREACHED();
|
||||
break;
|
||||
case RequestResponse::kSuccess:
|
||||
delegated_source_list_observer_->OnSelection();
|
||||
break;
|
||||
case RequestResponse::kUserCancelled:
|
||||
delegated_source_list_observer_->OnCancelled();
|
||||
break;
|
||||
case RequestResponse::kError:
|
||||
delegated_source_list_observer_->OnError();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::OnScreenCastSessionClosed() {
|
||||
if (!capturer_failed_) {
|
||||
options_.screencast_stream()->StopScreenCastStream();
|
||||
}
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::UpdateResolution(uint32_t width, uint32_t height) {
|
||||
if (!capturer_failed_) {
|
||||
options_.screencast_stream()->UpdateScreenCastStreamResolution(width,
|
||||
height);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::SetMaxFrameRate(uint32_t max_frame_rate) {
|
||||
if (!capturer_failed_) {
|
||||
options_.screencast_stream()->UpdateScreenCastStreamFrameRate(
|
||||
max_frame_rate);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::Start(Callback* callback) {
|
||||
RTC_DCHECK(!callback_);
|
||||
RTC_DCHECK(callback);
|
||||
|
||||
callback_ = callback;
|
||||
|
||||
if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
|
||||
screencast_portal->SetPersistMode(
|
||||
ScreenCastPortal::PersistMode::kTransient);
|
||||
if (selected_source_id_) {
|
||||
screencast_portal->SetRestoreToken(
|
||||
RestoreTokenManager::GetInstance().GetToken(selected_source_id_));
|
||||
}
|
||||
}
|
||||
|
||||
is_portal_open_ = true;
|
||||
portal_->Start();
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::CaptureFrame() {
|
||||
TRACE_EVENT0("webrtc", "BaseCapturerPipeWire::CaptureFrame");
|
||||
if (capturer_failed_) {
|
||||
// This could be recoverable if the source list is re-summoned; but for our
|
||||
// purposes this is fine, since it requires intervention to resolve and
|
||||
// essentially starts a new capture.
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t capture_start_time_nanos = rtc::TimeNanos();
|
||||
std::unique_ptr<DesktopFrame> frame =
|
||||
options_.screencast_stream()->CaptureFrame();
|
||||
|
||||
if (!frame || !frame->data()) {
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on
|
||||
// the frame, see ScreenCapturerX11::CaptureFrame.
|
||||
|
||||
frame->set_capturer_id(DesktopCapturerId::kWaylandCapturerLinux);
|
||||
frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) /
|
||||
rtc::kNumNanosecsPerMillisec);
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
|
||||
}
|
||||
|
||||
bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) {
|
||||
RTC_DCHECK(sources->size() == 0);
|
||||
// List of available screens is already presented by the xdg-desktop-portal,
|
||||
// so we just need a (valid) source id for any callers to pass around, even
|
||||
// though it doesn't mean anything to us. Until the user selects a source in
|
||||
// xdg-desktop-portal we'll just end up returning empty frames. Note that "0"
|
||||
// is often treated as a null/placeholder id, so we shouldn't use that.
|
||||
// TODO(https://crbug.com/1297671): Reconsider type of ID when plumbing
|
||||
// token that will enable stream re-use.
|
||||
sources->push_back({source_id_});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseCapturerPipeWire::SelectSource(SourceId id) {
|
||||
// Screen selection is handled by the xdg-desktop-portal.
|
||||
selected_source_id_ = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
DelegatedSourceListController*
|
||||
BaseCapturerPipeWire::GetDelegatedSourceListController() {
|
||||
return this;
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::Observe(Observer* observer) {
|
||||
RTC_DCHECK(!delegated_source_list_observer_ || !observer);
|
||||
delegated_source_list_observer_ = observer;
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::EnsureVisible() {
|
||||
RTC_DCHECK(callback_);
|
||||
if (is_portal_open_)
|
||||
return;
|
||||
|
||||
// Clear any previously selected state/capture
|
||||
portal_->Stop();
|
||||
options_.screencast_stream()->StopScreenCastStream();
|
||||
|
||||
// Get a new source id to reflect that the source has changed.
|
||||
source_id_ = RestoreTokenManager::GetInstance().GetUnusedId();
|
||||
|
||||
is_portal_open_ = true;
|
||||
portal_->Start();
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::EnsureHidden() {
|
||||
if (!is_portal_open_)
|
||||
return;
|
||||
|
||||
is_portal_open_ = false;
|
||||
portal_->Stop();
|
||||
}
|
||||
|
||||
SessionDetails BaseCapturerPipeWire::GetSessionDetails() {
|
||||
return portal_->GetSessionDetails();
|
||||
}
|
||||
|
||||
ScreenCastPortal* BaseCapturerPipeWire::GetScreenCastPortal() {
|
||||
return is_screencast_portal_ ? static_cast<ScreenCastPortal*>(portal_.get())
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void BaseCapturerPipeWire::SendFramesImmediately(bool send_frames_immediately) {
|
||||
send_frames_immediately_ = send_frames_immediately;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_
|
||||
|
||||
#include "modules/desktop_capture/delegated_source_list_controller.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h"
|
||||
#include "modules/desktop_capture/linux/wayland/screencast_portal.h"
|
||||
#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
|
||||
#include "modules/portal/portal_request_response.h"
|
||||
#include "modules/portal/xdg_desktop_portal_utils.h"
|
||||
#include "modules/portal/xdg_session_details.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RTC_EXPORT BaseCapturerPipeWire
|
||||
: public DesktopCapturer,
|
||||
public DelegatedSourceListController,
|
||||
public ScreenCastPortal::PortalNotifier {
|
||||
public:
|
||||
// Returns whether or not the current system can support capture via PipeWire.
|
||||
// This will only be true on Wayland systems that also have PipeWire
|
||||
// available, and thus may require dlopening PipeWire to determine if it is
|
||||
// available.
|
||||
static bool IsSupported();
|
||||
|
||||
BaseCapturerPipeWire(const DesktopCaptureOptions& options, CaptureType type);
|
||||
BaseCapturerPipeWire(
|
||||
const DesktopCaptureOptions& options,
|
||||
std::unique_ptr<xdg_portal::ScreenCapturePortalInterface> portal);
|
||||
~BaseCapturerPipeWire() override;
|
||||
|
||||
BaseCapturerPipeWire(const BaseCapturerPipeWire&) = delete;
|
||||
BaseCapturerPipeWire& operator=(const BaseCapturerPipeWire&) = delete;
|
||||
|
||||
// DesktopCapturer interface.
|
||||
void Start(Callback* delegate) override;
|
||||
void CaptureFrame() override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
DelegatedSourceListController* GetDelegatedSourceListController() override;
|
||||
void SetMaxFrameRate(uint32_t max_frame_rate) override;
|
||||
|
||||
// DelegatedSourceListController
|
||||
void Observe(Observer* observer) override;
|
||||
void EnsureVisible() override;
|
||||
void EnsureHidden() override;
|
||||
|
||||
// ScreenCastPortal::PortalNotifier interface.
|
||||
void OnScreenCastRequestResult(xdg_portal::RequestResponse result,
|
||||
uint32_t stream_node_id,
|
||||
int fd) override;
|
||||
void OnScreenCastSessionClosed() override;
|
||||
void UpdateResolution(uint32_t width, uint32_t height) override;
|
||||
|
||||
xdg_portal::SessionDetails GetSessionDetails();
|
||||
|
||||
// Notifies the callback about the available frames as soon as a frame is
|
||||
// received.
|
||||
void SendFramesImmediately(bool send_frames_immediately);
|
||||
|
||||
private:
|
||||
ScreenCastPortal* GetScreenCastPortal();
|
||||
|
||||
DesktopCaptureOptions options_ = {};
|
||||
Callback* callback_ = nullptr;
|
||||
bool send_frames_immediately_ = false;
|
||||
bool capturer_failed_ = false;
|
||||
bool is_screencast_portal_ = false;
|
||||
bool is_portal_open_ = false;
|
||||
|
||||
Observer* delegated_source_list_observer_ = nullptr;
|
||||
|
||||
// SourceId that is selected using SelectSource() and that we previously
|
||||
// returned in GetSourceList(). This should be a SourceId that has a restore
|
||||
// token associated with it and can be restored if we have required version
|
||||
// of xdg-desktop-portal.
|
||||
SourceId selected_source_id_ = 0;
|
||||
// SourceID we randomly generate and that is returned in GetSourceList() as
|
||||
// available source that will later get assigned to a restore token in order
|
||||
// to be restored later using SelectSource().
|
||||
SourceId source_id_ = 0;
|
||||
std::unique_ptr<xdg_portal::ScreenCapturePortalInterface> portal_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_BASE_CAPTURER_PIPEWIRE_H_
|
||||
|
|
@ -0,0 +1,755 @@
|
|||
/*
|
||||
* Copyright 2021 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h"
|
||||
|
||||
#include <asm/ioctl.h>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <linux/types.h>
|
||||
#include <spa/param/video/format-utils.h>
|
||||
#include <unistd.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/sanitizer.h"
|
||||
#include "rtc_base/string_encode.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// EGL
|
||||
typedef EGLBoolean (*eglBindAPI_func)(EGLenum api);
|
||||
typedef EGLContext (*eglCreateContext_func)(EGLDisplay dpy,
|
||||
EGLConfig config,
|
||||
EGLContext share_context,
|
||||
const EGLint* attrib_list);
|
||||
typedef EGLBoolean (*eglDestroyContext_func)(EGLDisplay display,
|
||||
EGLContext context);
|
||||
typedef EGLBoolean (*eglTerminate_func)(EGLDisplay display);
|
||||
typedef EGLImageKHR (*eglCreateImageKHR_func)(EGLDisplay dpy,
|
||||
EGLContext ctx,
|
||||
EGLenum target,
|
||||
EGLClientBuffer buffer,
|
||||
const EGLint* attrib_list);
|
||||
typedef EGLBoolean (*eglDestroyImageKHR_func)(EGLDisplay dpy,
|
||||
EGLImageKHR image);
|
||||
typedef EGLint (*eglGetError_func)(void);
|
||||
typedef void* (*eglGetProcAddress_func)(const char*);
|
||||
typedef EGLDisplay (*eglGetPlatformDisplayEXT_func)(EGLenum platform,
|
||||
void* native_display,
|
||||
const EGLint* attrib_list);
|
||||
typedef EGLDisplay (*eglGetPlatformDisplay_func)(EGLenum platform,
|
||||
void* native_display,
|
||||
const EGLAttrib* attrib_list);
|
||||
|
||||
typedef EGLBoolean (*eglInitialize_func)(EGLDisplay dpy,
|
||||
EGLint* major,
|
||||
EGLint* minor);
|
||||
typedef EGLBoolean (*eglMakeCurrent_func)(EGLDisplay dpy,
|
||||
EGLSurface draw,
|
||||
EGLSurface read,
|
||||
EGLContext ctx);
|
||||
typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func)(EGLDisplay dpy,
|
||||
EGLint max_formats,
|
||||
EGLint* formats,
|
||||
EGLint* num_formats);
|
||||
typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func)(EGLDisplay dpy,
|
||||
EGLint format,
|
||||
EGLint max_modifiers,
|
||||
EGLuint64KHR* modifiers,
|
||||
EGLBoolean* external_only,
|
||||
EGLint* num_modifiers);
|
||||
typedef const char* (*eglQueryString_func)(EGLDisplay dpy, EGLint name);
|
||||
typedef void (*glEGLImageTargetTexture2DOES_func)(GLenum target,
|
||||
GLeglImageOES image);
|
||||
|
||||
// This doesn't follow naming conventions in WebRTC, where the naming
|
||||
// should look like e.g. egl_bind_api instead of EglBindAPI, however
|
||||
// we named them according to the exported functions they map to for
|
||||
// consistency.
|
||||
eglBindAPI_func EglBindAPI = nullptr;
|
||||
eglCreateContext_func EglCreateContext = nullptr;
|
||||
eglDestroyContext_func EglDestroyContext = nullptr;
|
||||
eglTerminate_func EglTerminate = nullptr;
|
||||
eglCreateImageKHR_func EglCreateImageKHR = nullptr;
|
||||
eglDestroyImageKHR_func EglDestroyImageKHR = nullptr;
|
||||
eglGetError_func EglGetError = nullptr;
|
||||
eglGetProcAddress_func EglGetProcAddress = nullptr;
|
||||
eglGetPlatformDisplayEXT_func EglGetPlatformDisplayEXT = nullptr;
|
||||
eglGetPlatformDisplay_func EglGetPlatformDisplay = nullptr;
|
||||
eglInitialize_func EglInitialize = nullptr;
|
||||
eglMakeCurrent_func EglMakeCurrent = nullptr;
|
||||
eglQueryDmaBufFormatsEXT_func EglQueryDmaBufFormatsEXT = nullptr;
|
||||
eglQueryDmaBufModifiersEXT_func EglQueryDmaBufModifiersEXT = nullptr;
|
||||
eglQueryString_func EglQueryString = nullptr;
|
||||
glEGLImageTargetTexture2DOES_func GlEGLImageTargetTexture2DOES = nullptr;
|
||||
|
||||
// GL
|
||||
typedef void (*glBindTexture_func)(GLenum target, GLuint texture);
|
||||
typedef void (*glDeleteTextures_func)(GLsizei n, const GLuint* textures);
|
||||
typedef void (*glGenTextures_func)(GLsizei n, GLuint* textures);
|
||||
typedef GLenum (*glGetError_func)(void);
|
||||
typedef const GLubyte* (*glGetString_func)(GLenum name);
|
||||
typedef void (*glReadPixels_func)(GLint x,
|
||||
GLint y,
|
||||
GLsizei width,
|
||||
GLsizei height,
|
||||
GLenum format,
|
||||
GLenum type,
|
||||
void* data);
|
||||
typedef void (*glGenFramebuffers_func)(GLsizei n, GLuint* ids);
|
||||
typedef void (*glDeleteFramebuffers_func)(GLsizei n,
|
||||
const GLuint* framebuffers);
|
||||
typedef void (*glBindFramebuffer_func)(GLenum target, GLuint framebuffer);
|
||||
typedef void (*glFramebufferTexture2D_func)(GLenum target,
|
||||
GLenum attachment,
|
||||
GLenum textarget,
|
||||
GLuint texture,
|
||||
GLint level);
|
||||
typedef GLenum (*glCheckFramebufferStatus_func)(GLenum target);
|
||||
typedef void (*glTexParameteri_func)(GLenum target, GLenum pname, GLint param);
|
||||
typedef void* (*glXGetProcAddressARB_func)(const char*);
|
||||
|
||||
// This doesn't follow naming conventions in WebRTC, where the naming
|
||||
// should look like e.g. egl_bind_api instead of EglBindAPI, however
|
||||
// we named them according to the exported functions they map to for
|
||||
// consistency.
|
||||
glBindTexture_func GlBindTexture = nullptr;
|
||||
glDeleteTextures_func GlDeleteTextures = nullptr;
|
||||
glGenTextures_func GlGenTextures = nullptr;
|
||||
glGetError_func GlGetError = nullptr;
|
||||
glGetString_func GlGetString = nullptr;
|
||||
glReadPixels_func GlReadPixels = nullptr;
|
||||
glGenFramebuffers_func GlGenFramebuffers = nullptr;
|
||||
glDeleteFramebuffers_func GlDeleteFramebuffers = nullptr;
|
||||
glBindFramebuffer_func GlBindFramebuffer = nullptr;
|
||||
glFramebufferTexture2D_func GlFramebufferTexture2D = nullptr;
|
||||
glCheckFramebufferStatus_func GlCheckFramebufferStatus = nullptr;
|
||||
glTexParameteri_func GlTexParameteri = nullptr;
|
||||
glXGetProcAddressARB_func GlXGetProcAddressARB = nullptr;
|
||||
|
||||
static const std::string FormatGLError(GLenum err) {
|
||||
switch (err) {
|
||||
case GL_NO_ERROR:
|
||||
return "GL_NO_ERROR";
|
||||
case GL_INVALID_ENUM:
|
||||
return "GL_INVALID_ENUM";
|
||||
case GL_INVALID_VALUE:
|
||||
return "GL_INVALID_VALUE";
|
||||
case GL_INVALID_OPERATION:
|
||||
return "GL_INVALID_OPERATION";
|
||||
case GL_STACK_OVERFLOW:
|
||||
return "GL_STACK_OVERFLOW";
|
||||
case GL_STACK_UNDERFLOW:
|
||||
return "GL_STACK_UNDERFLOW";
|
||||
case GL_OUT_OF_MEMORY:
|
||||
return "GL_OUT_OF_MEMORY";
|
||||
default:
|
||||
return "GL error code: " + std::to_string(err);
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string FormatEGLError(EGLint err) {
|
||||
switch (err) {
|
||||
case EGL_NOT_INITIALIZED:
|
||||
return "EGL_NOT_INITIALIZED";
|
||||
case EGL_BAD_ACCESS:
|
||||
return "EGL_BAD_ACCESS";
|
||||
case EGL_BAD_ALLOC:
|
||||
return "EGL_BAD_ALLOC";
|
||||
case EGL_BAD_ATTRIBUTE:
|
||||
return "EGL_BAD_ATTRIBUTE";
|
||||
case EGL_BAD_CONTEXT:
|
||||
return "EGL_BAD_CONTEXT";
|
||||
case EGL_BAD_CONFIG:
|
||||
return "EGL_BAD_CONFIG";
|
||||
case EGL_BAD_CURRENT_SURFACE:
|
||||
return "EGL_BAD_CURRENT_SURFACE";
|
||||
case EGL_BAD_DISPLAY:
|
||||
return "EGL_BAD_DISPLAY";
|
||||
case EGL_BAD_SURFACE:
|
||||
return "EGL_BAD_SURFACE";
|
||||
case EGL_BAD_MATCH:
|
||||
return "EGL_BAD_MATCH";
|
||||
case EGL_BAD_PARAMETER:
|
||||
return "EGL_BAD_PARAMETER";
|
||||
case EGL_BAD_NATIVE_PIXMAP:
|
||||
return "EGL_BAD_NATIVE_PIXMAP";
|
||||
case EGL_BAD_NATIVE_WINDOW:
|
||||
return "EGL_BAD_NATIVE_WINDOW";
|
||||
case EGL_CONTEXT_LOST:
|
||||
return "EGL_CONTEXT_LOST";
|
||||
default:
|
||||
return "EGL error code: " + std::to_string(err);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t SpaPixelFormatToDrmFormat(uint32_t spa_format) {
|
||||
switch (spa_format) {
|
||||
case SPA_VIDEO_FORMAT_RGBA:
|
||||
return DRM_FORMAT_ABGR8888;
|
||||
case SPA_VIDEO_FORMAT_RGBx:
|
||||
return DRM_FORMAT_XBGR8888;
|
||||
case SPA_VIDEO_FORMAT_BGRA:
|
||||
return DRM_FORMAT_ARGB8888;
|
||||
case SPA_VIDEO_FORMAT_BGRx:
|
||||
return DRM_FORMAT_XRGB8888;
|
||||
default:
|
||||
return DRM_FORMAT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static void CloseLibrary(void* library) {
|
||||
if (library) {
|
||||
dlclose(library);
|
||||
library = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void* g_lib_egl = nullptr;
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
static bool OpenEGL() {
|
||||
g_lib_egl = dlopen("libEGL.so.1", RTLD_NOW | RTLD_GLOBAL);
|
||||
if (g_lib_egl) {
|
||||
EglGetProcAddress =
|
||||
(eglGetProcAddress_func)dlsym(g_lib_egl, "eglGetProcAddress");
|
||||
return EglGetProcAddress;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
static bool LoadEGL() {
|
||||
if (OpenEGL()) {
|
||||
EglBindAPI = (eglBindAPI_func)EglGetProcAddress("eglBindAPI");
|
||||
EglCreateContext =
|
||||
(eglCreateContext_func)EglGetProcAddress("eglCreateContext");
|
||||
EglDestroyContext =
|
||||
(eglDestroyContext_func)EglGetProcAddress("eglDestroyContext");
|
||||
EglTerminate = (eglTerminate_func)EglGetProcAddress("eglTerminate");
|
||||
EglCreateImageKHR =
|
||||
(eglCreateImageKHR_func)EglGetProcAddress("eglCreateImageKHR");
|
||||
EglDestroyImageKHR =
|
||||
(eglDestroyImageKHR_func)EglGetProcAddress("eglDestroyImageKHR");
|
||||
EglGetError = (eglGetError_func)EglGetProcAddress("eglGetError");
|
||||
EglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_func)EglGetProcAddress(
|
||||
"eglGetPlatformDisplayEXT");
|
||||
EglGetPlatformDisplay =
|
||||
(eglGetPlatformDisplay_func)EglGetProcAddress("eglGetPlatformDisplay");
|
||||
EglInitialize = (eglInitialize_func)EglGetProcAddress("eglInitialize");
|
||||
EglMakeCurrent = (eglMakeCurrent_func)EglGetProcAddress("eglMakeCurrent");
|
||||
EglQueryString = (eglQueryString_func)EglGetProcAddress("eglQueryString");
|
||||
GlEGLImageTargetTexture2DOES =
|
||||
(glEGLImageTargetTexture2DOES_func)EglGetProcAddress(
|
||||
"glEGLImageTargetTexture2DOES");
|
||||
|
||||
return EglBindAPI && EglCreateContext && EglCreateImageKHR &&
|
||||
EglTerminate && EglDestroyContext && EglDestroyImageKHR &&
|
||||
EglGetError && EglGetPlatformDisplayEXT && EglGetPlatformDisplay &&
|
||||
EglInitialize && EglMakeCurrent && EglQueryString &&
|
||||
GlEGLImageTargetTexture2DOES;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void* g_lib_gl = nullptr;
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
static bool OpenGL() {
|
||||
std::vector<std::string> names = {"libGL.so.1", "libGL.so"};
|
||||
for (const std::string& name : names) {
|
||||
g_lib_gl = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (g_lib_gl) {
|
||||
GlXGetProcAddressARB =
|
||||
(glXGetProcAddressARB_func)dlsym(g_lib_gl, "glXGetProcAddressARB");
|
||||
return GlXGetProcAddressARB;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
static bool LoadGL() {
|
||||
if (OpenGL()) {
|
||||
GlGetString = (glGetString_func)GlXGetProcAddressARB("glGetString");
|
||||
if (!GlGetString) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GlBindTexture = (glBindTexture_func)GlXGetProcAddressARB("glBindTexture");
|
||||
GlDeleteTextures =
|
||||
(glDeleteTextures_func)GlXGetProcAddressARB("glDeleteTextures");
|
||||
GlGenTextures = (glGenTextures_func)GlXGetProcAddressARB("glGenTextures");
|
||||
GlGetError = (glGetError_func)GlXGetProcAddressARB("glGetError");
|
||||
GlReadPixels = (glReadPixels_func)GlXGetProcAddressARB("glReadPixels");
|
||||
GlGenFramebuffers =
|
||||
(glGenFramebuffers_func)GlXGetProcAddressARB("glGenFramebuffers");
|
||||
GlDeleteFramebuffers =
|
||||
(glDeleteFramebuffers_func)GlXGetProcAddressARB("glDeleteFramebuffers");
|
||||
GlBindFramebuffer =
|
||||
(glBindFramebuffer_func)GlXGetProcAddressARB("glBindFramebuffer");
|
||||
GlFramebufferTexture2D = (glFramebufferTexture2D_func)GlXGetProcAddressARB(
|
||||
"glFramebufferTexture2D");
|
||||
GlCheckFramebufferStatus =
|
||||
(glCheckFramebufferStatus_func)GlXGetProcAddressARB(
|
||||
"glCheckFramebufferStatus");
|
||||
|
||||
GlTexParameteri =
|
||||
(glTexParameteri_func)GlXGetProcAddressARB("glTexParameteri");
|
||||
|
||||
return GlBindTexture && GlDeleteTextures && GlGenTextures && GlGetError &&
|
||||
GlReadPixels && GlGenFramebuffers && GlDeleteFramebuffers &&
|
||||
GlBindFramebuffer && GlFramebufferTexture2D &&
|
||||
GlCheckFramebufferStatus && GlTexParameteri;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
EglDmaBuf::EglDmaBuf() {
|
||||
if (!LoadEGL()) {
|
||||
RTC_LOG(LS_ERROR) << "Unable to load EGL entry functions.";
|
||||
CloseLibrary(g_lib_egl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LoadGL()) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to load OpenGL entry functions.";
|
||||
CloseLibrary(g_lib_gl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetClientExtensions(EGL_NO_DISPLAY, EGL_EXTENSIONS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_platform_base_ext = false;
|
||||
bool has_platform_gbm_ext = false;
|
||||
bool has_khr_platform_gbm_ext = false;
|
||||
|
||||
for (const auto& extension : egl_.extensions) {
|
||||
if (extension == "EGL_EXT_platform_base") {
|
||||
has_platform_base_ext = true;
|
||||
continue;
|
||||
} else if (extension == "EGL_MESA_platform_gbm") {
|
||||
has_platform_gbm_ext = true;
|
||||
continue;
|
||||
} else if (extension == "EGL_KHR_platform_gbm") {
|
||||
has_khr_platform_gbm_ext = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_platform_base_ext || !has_platform_gbm_ext ||
|
||||
!has_khr_platform_gbm_ext) {
|
||||
RTC_LOG(LS_ERROR) << "One of required EGL extensions is missing";
|
||||
return;
|
||||
}
|
||||
|
||||
egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR,
|
||||
(void*)EGL_DEFAULT_DISPLAY, nullptr);
|
||||
|
||||
if (egl_.display == EGL_NO_DISPLAY) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to obtain default EGL display: "
|
||||
<< FormatEGLError(EglGetError()) << "\n"
|
||||
<< "Defaulting to using first available render node";
|
||||
absl::optional<std::string> render_node = GetRenderNode();
|
||||
if (!render_node) {
|
||||
return;
|
||||
}
|
||||
|
||||
drm_fd_ = open(render_node->c_str(), O_RDWR);
|
||||
|
||||
if (drm_fd_ < 0) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to open drm render node: "
|
||||
<< strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
gbm_device_ = gbm_create_device(drm_fd_);
|
||||
|
||||
if (!gbm_device_) {
|
||||
RTC_LOG(LS_ERROR) << "Cannot create GBM device: " << strerror(errno);
|
||||
close(drm_fd_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use eglGetPlatformDisplayEXT() to get the display pointer
|
||||
// if the implementation supports it.
|
||||
egl_.display =
|
||||
EglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm_device_, nullptr);
|
||||
}
|
||||
|
||||
if (egl_.display == EGL_NO_DISPLAY) {
|
||||
RTC_LOG(LS_ERROR) << "Error during obtaining EGL display: "
|
||||
<< FormatEGLError(EglGetError());
|
||||
return;
|
||||
}
|
||||
|
||||
EGLint major, minor;
|
||||
if (EglInitialize(egl_.display, &major, &minor) == EGL_FALSE) {
|
||||
RTC_LOG(LS_ERROR) << "Error during eglInitialize: "
|
||||
<< FormatEGLError(EglGetError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (EglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
|
||||
RTC_LOG(LS_ERROR) << "bind OpenGL API failed";
|
||||
return;
|
||||
}
|
||||
|
||||
egl_.context =
|
||||
EglCreateContext(egl_.display, nullptr, EGL_NO_CONTEXT, nullptr);
|
||||
|
||||
if (egl_.context == EGL_NO_CONTEXT) {
|
||||
RTC_LOG(LS_ERROR) << "Couldn't create EGL context: "
|
||||
<< FormatGLError(EglGetError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetClientExtensions(egl_.display, EGL_EXTENSIONS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_image_dma_buf_import_modifiers_ext = false;
|
||||
|
||||
for (const auto& extension : egl_.extensions) {
|
||||
if (extension == "EGL_EXT_image_dma_buf_import") {
|
||||
has_image_dma_buf_import_ext_ = true;
|
||||
continue;
|
||||
} else if (extension == "EGL_EXT_image_dma_buf_import_modifiers") {
|
||||
has_image_dma_buf_import_modifiers_ext = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_image_dma_buf_import_ext_ && has_image_dma_buf_import_modifiers_ext) {
|
||||
EglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func)EglGetProcAddress(
|
||||
"eglQueryDmaBufFormatsEXT");
|
||||
EglQueryDmaBufModifiersEXT =
|
||||
(eglQueryDmaBufModifiersEXT_func)EglGetProcAddress(
|
||||
"eglQueryDmaBufModifiersEXT");
|
||||
}
|
||||
|
||||
RTC_LOG(LS_INFO) << "Egl initialization succeeded";
|
||||
egl_initialized_ = true;
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
EglDmaBuf::~EglDmaBuf() {
|
||||
if (gbm_device_) {
|
||||
gbm_device_destroy(gbm_device_);
|
||||
close(drm_fd_);
|
||||
}
|
||||
|
||||
if (egl_.context != EGL_NO_CONTEXT) {
|
||||
EglDestroyContext(egl_.display, egl_.context);
|
||||
}
|
||||
|
||||
if (egl_.display != EGL_NO_DISPLAY) {
|
||||
EglTerminate(egl_.display);
|
||||
}
|
||||
|
||||
if (fbo_) {
|
||||
GlDeleteFramebuffers(1, &fbo_);
|
||||
}
|
||||
|
||||
if (texture_) {
|
||||
GlDeleteTextures(1, &texture_);
|
||||
}
|
||||
|
||||
// BUG: crbug.com/1290566
|
||||
// Closing libEGL.so.1 when using NVidia drivers causes a crash
|
||||
// when EglGetPlatformDisplayEXT() is used, at least this one is enough
|
||||
// to be called to make it crash.
|
||||
// It also looks that libepoxy and glad don't dlclose it either
|
||||
// CloseLibrary(g_lib_egl);
|
||||
// CloseLibrary(g_lib_gl);
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
bool EglDmaBuf::GetClientExtensions(EGLDisplay dpy, EGLint name) {
|
||||
// Get the list of client extensions
|
||||
const char* client_extensions_cstring = EglQueryString(dpy, name);
|
||||
if (!client_extensions_cstring) {
|
||||
// If eglQueryString() returned NULL, the implementation doesn't support
|
||||
// EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error.
|
||||
RTC_LOG(LS_ERROR) << "No client extensions defined! "
|
||||
<< FormatEGLError(EglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<absl::string_view> client_extensions =
|
||||
rtc::split(client_extensions_cstring, ' ');
|
||||
for (const auto& extension : client_extensions) {
|
||||
egl_.extensions.push_back(std::string(extension));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
bool EglDmaBuf::ImageFromDmaBuf(const DesktopSize& size,
|
||||
uint32_t format,
|
||||
const std::vector<PlaneData>& plane_datas,
|
||||
uint64_t modifier,
|
||||
const DesktopVector& offset,
|
||||
const DesktopSize& buffer_size,
|
||||
uint8_t* data) {
|
||||
if (!egl_initialized_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plane_datas.size() <= 0) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to process buffer: invalid number of planes";
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLint attribs[47];
|
||||
int atti = 0;
|
||||
|
||||
attribs[atti++] = EGL_WIDTH;
|
||||
attribs[atti++] = static_cast<EGLint>(size.width());
|
||||
attribs[atti++] = EGL_HEIGHT;
|
||||
attribs[atti++] = static_cast<EGLint>(size.height());
|
||||
attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
|
||||
attribs[atti++] = SpaPixelFormatToDrmFormat(format);
|
||||
|
||||
if (plane_datas.size() > 0) {
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT;
|
||||
attribs[atti++] = plane_datas[0].fd;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
|
||||
attribs[atti++] = plane_datas[0].offset;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
|
||||
attribs[atti++] = plane_datas[0].stride;
|
||||
|
||||
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
|
||||
attribs[atti++] = modifier & 0xFFFFFFFF;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
|
||||
attribs[atti++] = modifier >> 32;
|
||||
}
|
||||
}
|
||||
|
||||
if (plane_datas.size() > 1) {
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT;
|
||||
attribs[atti++] = plane_datas[1].fd;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
|
||||
attribs[atti++] = plane_datas[1].offset;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
|
||||
attribs[atti++] = plane_datas[1].stride;
|
||||
|
||||
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
|
||||
attribs[atti++] = modifier & 0xFFFFFFFF;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
|
||||
attribs[atti++] = modifier >> 32;
|
||||
}
|
||||
}
|
||||
|
||||
if (plane_datas.size() > 2) {
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT;
|
||||
attribs[atti++] = plane_datas[2].fd;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
|
||||
attribs[atti++] = plane_datas[2].offset;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
|
||||
attribs[atti++] = plane_datas[2].stride;
|
||||
|
||||
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
|
||||
attribs[atti++] = modifier & 0xFFFFFFFF;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
|
||||
attribs[atti++] = modifier >> 32;
|
||||
}
|
||||
}
|
||||
|
||||
if (plane_datas.size() > 3) {
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE3_FD_EXT;
|
||||
attribs[atti++] = plane_datas[3].fd;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT;
|
||||
attribs[atti++] = plane_datas[3].offset;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE3_PITCH_EXT;
|
||||
attribs[atti++] = plane_datas[3].stride;
|
||||
|
||||
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT;
|
||||
attribs[atti++] = modifier & 0xFFFFFFFF;
|
||||
attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT;
|
||||
attribs[atti++] = modifier >> 32;
|
||||
}
|
||||
}
|
||||
|
||||
attribs[atti++] = EGL_NONE;
|
||||
|
||||
// bind context to render thread
|
||||
EglMakeCurrent(egl_.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_.context);
|
||||
|
||||
// create EGL image from attribute list
|
||||
EGLImageKHR image = EglCreateImageKHR(
|
||||
egl_.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs);
|
||||
|
||||
if (image == EGL_NO_IMAGE) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to record frame: Error creating EGLImage - "
|
||||
<< FormatEGLError(EglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// create GL 2D texture for framebuffer
|
||||
if (!texture_) {
|
||||
GlGenTextures(1, &texture_);
|
||||
GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
GlBindTexture(GL_TEXTURE_2D, texture_);
|
||||
GlEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
|
||||
|
||||
if (!fbo_) {
|
||||
GlGenFramebuffers(1, &fbo_);
|
||||
}
|
||||
|
||||
GlBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||
GlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
texture_, 0);
|
||||
if (GlCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to bind DMA buf framebuffer";
|
||||
EglDestroyImageKHR(egl_.display, image);
|
||||
return false;
|
||||
}
|
||||
|
||||
GLenum gl_format = GL_BGRA;
|
||||
switch (format) {
|
||||
case SPA_VIDEO_FORMAT_RGBx:
|
||||
gl_format = GL_RGBA;
|
||||
break;
|
||||
case SPA_VIDEO_FORMAT_RGBA:
|
||||
gl_format = GL_RGBA;
|
||||
break;
|
||||
case SPA_VIDEO_FORMAT_BGRx:
|
||||
gl_format = GL_BGRA;
|
||||
break;
|
||||
default:
|
||||
gl_format = GL_BGRA;
|
||||
break;
|
||||
}
|
||||
|
||||
GlReadPixels(offset.x(), offset.y(), buffer_size.width(),
|
||||
buffer_size.height(), gl_format, GL_UNSIGNED_BYTE, data);
|
||||
|
||||
const GLenum error = GlGetError();
|
||||
if (error) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to get image from DMA buffer.";
|
||||
}
|
||||
|
||||
EglDestroyImageKHR(egl_.display, image);
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
std::vector<uint64_t> EglDmaBuf::QueryDmaBufModifiers(uint32_t format) {
|
||||
if (!egl_initialized_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Explicit modifiers not supported, return just DRM_FORMAT_MOD_INVALID as we
|
||||
// can still use modifier-less DMA-BUFs if we have required extension
|
||||
if (EglQueryDmaBufFormatsEXT == nullptr ||
|
||||
EglQueryDmaBufModifiersEXT == nullptr) {
|
||||
return has_image_dma_buf_import_ext_
|
||||
? std::vector<uint64_t>{DRM_FORMAT_MOD_INVALID}
|
||||
: std::vector<uint64_t>{};
|
||||
}
|
||||
|
||||
uint32_t drm_format = SpaPixelFormatToDrmFormat(format);
|
||||
// Should never happen as it's us who controls the list of supported formats
|
||||
RTC_DCHECK(drm_format != DRM_FORMAT_INVALID);
|
||||
|
||||
EGLint count = 0;
|
||||
EGLBoolean success =
|
||||
EglQueryDmaBufFormatsEXT(egl_.display, 0, nullptr, &count);
|
||||
|
||||
if (!success || !count) {
|
||||
RTC_LOG(LS_WARNING) << "Cannot query the number of formats.";
|
||||
return {DRM_FORMAT_MOD_INVALID};
|
||||
}
|
||||
|
||||
std::vector<uint32_t> formats(count);
|
||||
if (!EglQueryDmaBufFormatsEXT(egl_.display, count,
|
||||
reinterpret_cast<EGLint*>(formats.data()),
|
||||
&count)) {
|
||||
RTC_LOG(LS_WARNING) << "Cannot query a list of formats.";
|
||||
return {DRM_FORMAT_MOD_INVALID};
|
||||
}
|
||||
|
||||
if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) {
|
||||
RTC_LOG(LS_WARNING) << "Format " << drm_format
|
||||
<< " not supported for modifiers.";
|
||||
return {DRM_FORMAT_MOD_INVALID};
|
||||
}
|
||||
|
||||
success = EglQueryDmaBufModifiersEXT(egl_.display, drm_format, 0, nullptr,
|
||||
nullptr, &count);
|
||||
|
||||
if (!success || !count) {
|
||||
RTC_LOG(LS_WARNING) << "Cannot query the number of modifiers.";
|
||||
return {DRM_FORMAT_MOD_INVALID};
|
||||
}
|
||||
|
||||
std::vector<uint64_t> modifiers(count);
|
||||
if (!EglQueryDmaBufModifiersEXT(egl_.display, drm_format, count,
|
||||
modifiers.data(), nullptr, &count)) {
|
||||
RTC_LOG(LS_WARNING) << "Cannot query a list of modifiers.";
|
||||
}
|
||||
|
||||
// Support modifier-less buffers
|
||||
modifiers.push_back(DRM_FORMAT_MOD_INVALID);
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
absl::optional<std::string> EglDmaBuf::GetRenderNode() {
|
||||
int max_devices = drmGetDevices2(0, nullptr, 0);
|
||||
if (max_devices <= 0) {
|
||||
RTC_LOG(LS_ERROR) << "drmGetDevices2() has not found any devices (errno="
|
||||
<< -max_devices << ")";
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
std::vector<drmDevicePtr> devices(max_devices);
|
||||
int ret = drmGetDevices2(0, devices.data(), max_devices);
|
||||
if (ret < 0) {
|
||||
RTC_LOG(LS_ERROR) << "drmGetDevices2() returned an error " << ret;
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
std::string render_node;
|
||||
|
||||
for (const drmDevicePtr& device : devices) {
|
||||
if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
|
||||
render_node = device->nodes[DRM_NODE_RENDER];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drmFreeDevices(devices.data(), ret);
|
||||
return render_node;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2021 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_
|
||||
|
||||
#include <epoxy/egl.h>
|
||||
#include <epoxy/gl.h>
|
||||
#include <gbm.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class EglDmaBuf {
|
||||
public:
|
||||
struct EGLStruct {
|
||||
std::vector<std::string> extensions;
|
||||
EGLDisplay display = EGL_NO_DISPLAY;
|
||||
EGLContext context = EGL_NO_CONTEXT;
|
||||
};
|
||||
|
||||
struct PlaneData {
|
||||
int32_t fd;
|
||||
uint32_t stride;
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
EglDmaBuf();
|
||||
~EglDmaBuf();
|
||||
|
||||
// Returns whether the image was successfully imported from
|
||||
// given DmaBuf and its parameters
|
||||
bool ImageFromDmaBuf(const DesktopSize& size,
|
||||
uint32_t format,
|
||||
const std::vector<PlaneData>& plane_datas,
|
||||
uint64_t modifiers,
|
||||
const DesktopVector& offset,
|
||||
const DesktopSize& buffer_size,
|
||||
uint8_t* data);
|
||||
std::vector<uint64_t> QueryDmaBufModifiers(uint32_t format);
|
||||
|
||||
bool IsEglInitialized() const { return egl_initialized_; }
|
||||
|
||||
private:
|
||||
bool GetClientExtensions(EGLDisplay dpy, EGLint name);
|
||||
|
||||
bool egl_initialized_ = false;
|
||||
bool has_image_dma_buf_import_ext_ = false;
|
||||
int32_t drm_fd_ = -1; // for GBM buffer mmap
|
||||
gbm_device* gbm_device_ = nullptr; // for passed GBM buffer retrieval
|
||||
|
||||
GLuint fbo_ = 0;
|
||||
GLuint texture_ = 0;
|
||||
EGLStruct egl_;
|
||||
|
||||
absl::optional<std::string> GetRenderNode();
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_EGL_DMABUF_H_
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/wayland/mouse_cursor_monitor_pipewire.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
MouseCursorMonitorPipeWire::MouseCursorMonitorPipeWire(
|
||||
const DesktopCaptureOptions& options)
|
||||
: options_(options) {
|
||||
sequence_checker_.Detach();
|
||||
}
|
||||
|
||||
MouseCursorMonitorPipeWire::~MouseCursorMonitorPipeWire() {}
|
||||
|
||||
void MouseCursorMonitorPipeWire::Init(Callback* callback, Mode mode) {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
RTC_DCHECK(!callback_);
|
||||
RTC_DCHECK(callback);
|
||||
|
||||
callback_ = callback;
|
||||
mode_ = mode;
|
||||
}
|
||||
|
||||
void MouseCursorMonitorPipeWire::Capture() {
|
||||
RTC_DCHECK_RUN_ON(&sequence_checker_);
|
||||
RTC_DCHECK(callback_);
|
||||
|
||||
std::unique_ptr<MouseCursor> mouse_cursor =
|
||||
options_.screencast_stream()->CaptureCursor();
|
||||
|
||||
if (mouse_cursor && mouse_cursor->image()->data()) {
|
||||
callback_->OnMouseCursor(mouse_cursor.release());
|
||||
}
|
||||
|
||||
if (mode_ == SHAPE_AND_POSITION) {
|
||||
absl::optional<DesktopVector> mouse_cursor_position =
|
||||
options_.screencast_stream()->CaptureCursorPosition();
|
||||
if (mouse_cursor_position) {
|
||||
callback_->OnMouseCursorPosition(mouse_cursor_position.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "api/sequence_checker.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h"
|
||||
#include "modules/desktop_capture/mouse_cursor.h"
|
||||
#include "modules/desktop_capture/mouse_cursor_monitor.h"
|
||||
#include "rtc_base/system/no_unique_address.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class MouseCursorMonitorPipeWire : public MouseCursorMonitor {
|
||||
public:
|
||||
explicit MouseCursorMonitorPipeWire(const DesktopCaptureOptions& options);
|
||||
~MouseCursorMonitorPipeWire() override;
|
||||
|
||||
// MouseCursorMonitor:
|
||||
void Init(Callback* callback, Mode mode) override;
|
||||
void Capture() override;
|
||||
|
||||
DesktopCaptureOptions options_ RTC_GUARDED_BY(sequence_checker_);
|
||||
Callback* callback_ RTC_GUARDED_BY(sequence_checker_) = nullptr;
|
||||
Mode mode_ RTC_GUARDED_BY(sequence_checker_) = SHAPE_AND_POSITION;
|
||||
RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_MOUSE_CURSOR_MONITOR_PIPEWIRE_H_
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_
|
||||
|
||||
// TODO(bugs.webrtc.org/14187): remove when all users are gone
|
||||
#include "modules/portal/portal_request_response.h"
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_PORTAL_REQUEST_RESPONSE_H_
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/wayland/restore_token_manager.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// static
|
||||
RestoreTokenManager& RestoreTokenManager::GetInstance() {
|
||||
static webrtc::RestoreTokenManager* manager = new RestoreTokenManager();
|
||||
return *manager;
|
||||
}
|
||||
|
||||
void RestoreTokenManager::AddToken(DesktopCapturer::SourceId id,
|
||||
const std::string& token) {
|
||||
restore_tokens_.insert({id, token});
|
||||
}
|
||||
|
||||
std::string RestoreTokenManager::GetToken(DesktopCapturer::SourceId id) {
|
||||
const std::string token = restore_tokens_[id];
|
||||
return token;
|
||||
}
|
||||
|
||||
DesktopCapturer::SourceId RestoreTokenManager::GetUnusedId() {
|
||||
return ++last_source_id_;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RestoreTokenManager {
|
||||
public:
|
||||
RestoreTokenManager(const RestoreTokenManager& manager) = delete;
|
||||
RestoreTokenManager& operator=(const RestoreTokenManager& manager) = delete;
|
||||
|
||||
static RestoreTokenManager& GetInstance();
|
||||
|
||||
void AddToken(DesktopCapturer::SourceId id, const std::string& token);
|
||||
std::string GetToken(DesktopCapturer::SourceId id);
|
||||
|
||||
// Returns a source ID which does not have any token associated with it yet.
|
||||
DesktopCapturer::SourceId GetUnusedId();
|
||||
|
||||
private:
|
||||
RestoreTokenManager() = default;
|
||||
~RestoreTokenManager() = default;
|
||||
|
||||
DesktopCapturer::SourceId last_source_id_ = 0;
|
||||
|
||||
std::unordered_map<DesktopCapturer::SourceId, std::string> restore_tokens_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_
|
||||
|
||||
// TODO(bugs.webrtc.org/14187): remove when all users are gone
|
||||
#include "modules/portal/scoped_glib.h"
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCOPED_GLIB_H_
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "modules/portal/xdg_desktop_portal_utils.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace xdg_portal {
|
||||
|
||||
void ScreenCapturePortalInterface::RequestSessionUsingProxy(
|
||||
GAsyncResult* result) {
|
||||
Scoped<GError> error;
|
||||
GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive());
|
||||
if (!proxy) {
|
||||
// Ignore the error caused by user cancelling the request via `cancellable_`
|
||||
if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
return;
|
||||
RTC_LOG(LS_ERROR) << "Failed to get a proxy for the portal: "
|
||||
<< error->message;
|
||||
OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
RTC_LOG(LS_INFO) << "Successfully created proxy for the portal.";
|
||||
RequestSession(proxy);
|
||||
}
|
||||
|
||||
void ScreenCapturePortalInterface::OnSessionRequestResult(
|
||||
GDBusProxy* proxy,
|
||||
GAsyncResult* result) {
|
||||
Scoped<GError> error;
|
||||
Scoped<GVariant> variant(
|
||||
g_dbus_proxy_call_finish(proxy, result, error.receive()));
|
||||
if (!variant) {
|
||||
// Ignore the error caused by user cancelling the request via `cancellable_`
|
||||
if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
return;
|
||||
RTC_LOG(LS_ERROR) << "Failed to request session: " << error->message;
|
||||
OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
RTC_LOG(LS_INFO) << "Initializing the session.";
|
||||
|
||||
Scoped<char> handle;
|
||||
g_variant_get_child(variant.get(), /*index=*/0, /*format_string=*/"o",
|
||||
&handle);
|
||||
if (!handle) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to initialize the session.";
|
||||
OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturePortalInterface::RegisterSessionClosedSignalHandler(
|
||||
const SessionClosedSignalHandler session_close_signal_handler,
|
||||
GVariant* parameters,
|
||||
GDBusConnection* connection,
|
||||
std::string& session_handle,
|
||||
guint& session_closed_signal_id) {
|
||||
uint32_t portal_response = 2;
|
||||
Scoped<GVariant> response_data;
|
||||
g_variant_get(parameters, /*format_string=*/"(u@a{sv})", &portal_response,
|
||||
response_data.receive());
|
||||
|
||||
if (RequestResponseFromPortalResponse(portal_response) !=
|
||||
RequestResponse::kSuccess) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to request the session subscription.";
|
||||
OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
Scoped<GVariant> g_session_handle(
|
||||
g_variant_lookup_value(response_data.get(), /*key=*/"session_handle",
|
||||
/*expected_type=*/nullptr));
|
||||
session_handle = g_variant_get_string(
|
||||
/*value=*/g_session_handle.get(), /*length=*/nullptr);
|
||||
|
||||
if (session_handle.empty()) {
|
||||
RTC_LOG(LS_ERROR) << "Could not get session handle despite valid response";
|
||||
OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
session_closed_signal_id = g_dbus_connection_signal_subscribe(
|
||||
connection, kDesktopBusName, kSessionInterfaceName, /*member=*/"Closed",
|
||||
session_handle.c_str(), /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NONE,
|
||||
session_close_signal_handler, this, /*user_data_free_func=*/nullptr);
|
||||
}
|
||||
|
||||
void ScreenCapturePortalInterface::OnStartRequestResult(GDBusProxy* proxy,
|
||||
GAsyncResult* result) {
|
||||
Scoped<GError> error;
|
||||
Scoped<GVariant> variant(
|
||||
g_dbus_proxy_call_finish(proxy, result, error.receive()));
|
||||
if (!variant) {
|
||||
if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
return;
|
||||
RTC_LOG(LS_ERROR) << "Failed to start the portal session: "
|
||||
<< error->message;
|
||||
OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
Scoped<char> handle;
|
||||
g_variant_get_child(variant.get(), 0, "o", handle.receive());
|
||||
if (!handle) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to initialize the start portal session.";
|
||||
OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
RTC_LOG(LS_INFO) << "Subscribed to the start signal.";
|
||||
}
|
||||
|
||||
} // namespace xdg_portal
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "modules/portal/portal_request_response.h"
|
||||
#include "modules/portal/scoped_glib.h"
|
||||
#include "modules/portal/xdg_desktop_portal_utils.h"
|
||||
#include "modules/portal/xdg_session_details.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace xdg_portal {
|
||||
|
||||
using SessionClosedSignalHandler = void (*)(GDBusConnection*,
|
||||
const char*,
|
||||
const char*,
|
||||
const char*,
|
||||
const char*,
|
||||
GVariant*,
|
||||
gpointer);
|
||||
|
||||
// A base class for XDG desktop portals that can capture desktop/screen.
|
||||
// Note: downstream clients inherit from this class so it is advisable to
|
||||
// provide a default implementation of any new virtual methods that may be added
|
||||
// to this class.
|
||||
class RTC_EXPORT ScreenCapturePortalInterface {
|
||||
public:
|
||||
virtual ~ScreenCapturePortalInterface() {}
|
||||
// Gets details about the session such as session handle.
|
||||
virtual xdg_portal::SessionDetails GetSessionDetails() { return {}; }
|
||||
// Starts the portal setup.
|
||||
virtual void Start() {}
|
||||
|
||||
// Stops and cleans up the portal.
|
||||
virtual void Stop() {}
|
||||
|
||||
// Notifies observers about the success/fail state of the portal
|
||||
// request/response.
|
||||
virtual void OnPortalDone(xdg_portal::RequestResponse result) {}
|
||||
// Sends a create session request to the portal.
|
||||
virtual void RequestSession(GDBusProxy* proxy) {}
|
||||
|
||||
// Following methods should not be made virtual as they share a common
|
||||
// implementation between portals.
|
||||
|
||||
// Requests portal session using the proxy object.
|
||||
void RequestSessionUsingProxy(GAsyncResult* result);
|
||||
// Handles the session request result.
|
||||
void OnSessionRequestResult(GDBusProxy* proxy, GAsyncResult* result);
|
||||
// Subscribes to session close signal and sets up a handler for it.
|
||||
void RegisterSessionClosedSignalHandler(
|
||||
const SessionClosedSignalHandler session_close_signal_handler,
|
||||
GVariant* parameters,
|
||||
GDBusConnection* connection,
|
||||
std::string& session_handle,
|
||||
guint& session_closed_signal_id);
|
||||
// Handles the result of session start request.
|
||||
void OnStartRequestResult(GDBusProxy* proxy, GAsyncResult* result);
|
||||
};
|
||||
|
||||
} // namespace xdg_portal
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREEN_CAPTURE_PORTAL_INTERFACE_H_
|
||||
|
|
@ -0,0 +1,471 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/wayland/screencast_portal.h"
|
||||
|
||||
#include <gio/gunixfdlist.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "modules/portal/scoped_glib.h"
|
||||
#include "modules/portal/xdg_desktop_portal_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
using xdg_portal::kScreenCastInterfaceName;
|
||||
using xdg_portal::PrepareSignalHandle;
|
||||
using xdg_portal::RequestResponse;
|
||||
using xdg_portal::RequestResponseFromPortalResponse;
|
||||
using xdg_portal::RequestSessionProxy;
|
||||
using xdg_portal::SetupRequestResponseSignal;
|
||||
using xdg_portal::SetupSessionRequestHandlers;
|
||||
using xdg_portal::StartSessionRequest;
|
||||
using xdg_portal::TearDownSession;
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
ScreenCastPortal::CaptureSourceType ScreenCastPortal::ToCaptureSourceType(
|
||||
CaptureType type) {
|
||||
switch (type) {
|
||||
case CaptureType::kScreen:
|
||||
return ScreenCastPortal::CaptureSourceType::kScreen;
|
||||
case CaptureType::kWindow:
|
||||
return ScreenCastPortal::CaptureSourceType::kWindow;
|
||||
case CaptureType::kAnyScreenContent:
|
||||
return ScreenCastPortal::CaptureSourceType::kAnyScreenContent;
|
||||
}
|
||||
}
|
||||
|
||||
ScreenCastPortal::ScreenCastPortal(CaptureType type, PortalNotifier* notifier)
|
||||
: ScreenCastPortal(type,
|
||||
notifier,
|
||||
OnProxyRequested,
|
||||
OnSourcesRequestResponseSignal,
|
||||
this) {}
|
||||
|
||||
ScreenCastPortal::ScreenCastPortal(
|
||||
CaptureType type,
|
||||
PortalNotifier* notifier,
|
||||
ProxyRequestResponseHandler proxy_request_response_handler,
|
||||
SourcesRequestResponseSignalHandler sources_request_response_signal_handler,
|
||||
gpointer user_data,
|
||||
bool prefer_cursor_embedded)
|
||||
: notifier_(notifier),
|
||||
capture_source_type_(ToCaptureSourceType(type)),
|
||||
cursor_mode_(prefer_cursor_embedded ? CursorMode::kEmbedded
|
||||
: CursorMode::kMetadata),
|
||||
proxy_request_response_handler_(proxy_request_response_handler),
|
||||
sources_request_response_signal_handler_(
|
||||
sources_request_response_signal_handler),
|
||||
user_data_(user_data) {}
|
||||
|
||||
ScreenCastPortal::~ScreenCastPortal() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void ScreenCastPortal::Stop() {
|
||||
UnsubscribeSignalHandlers();
|
||||
TearDownSession(std::move(session_handle_), proxy_, cancellable_,
|
||||
connection_);
|
||||
session_handle_ = "";
|
||||
cancellable_ = nullptr;
|
||||
proxy_ = nullptr;
|
||||
restore_token_ = "";
|
||||
|
||||
if (pw_fd_ != kInvalidPipeWireFd) {
|
||||
close(pw_fd_);
|
||||
pw_fd_ = kInvalidPipeWireFd;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCastPortal::UnsubscribeSignalHandlers() {
|
||||
if (start_request_signal_id_) {
|
||||
g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_);
|
||||
start_request_signal_id_ = 0;
|
||||
}
|
||||
|
||||
if (sources_request_signal_id_) {
|
||||
g_dbus_connection_signal_unsubscribe(connection_,
|
||||
sources_request_signal_id_);
|
||||
sources_request_signal_id_ = 0;
|
||||
}
|
||||
|
||||
if (session_request_signal_id_) {
|
||||
g_dbus_connection_signal_unsubscribe(connection_,
|
||||
session_request_signal_id_);
|
||||
session_request_signal_id_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCastPortal::SetSessionDetails(
|
||||
const xdg_portal::SessionDetails& session_details) {
|
||||
if (session_details.proxy) {
|
||||
proxy_ = session_details.proxy;
|
||||
connection_ = g_dbus_proxy_get_connection(proxy_);
|
||||
}
|
||||
if (session_details.cancellable) {
|
||||
cancellable_ = session_details.cancellable;
|
||||
}
|
||||
if (!session_details.session_handle.empty()) {
|
||||
session_handle_ = session_details.session_handle;
|
||||
}
|
||||
if (session_details.pipewire_stream_node_id) {
|
||||
pw_stream_node_id_ = session_details.pipewire_stream_node_id;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCastPortal::Start() {
|
||||
cancellable_ = g_cancellable_new();
|
||||
RequestSessionProxy(kScreenCastInterfaceName, proxy_request_response_handler_,
|
||||
cancellable_, this);
|
||||
}
|
||||
|
||||
xdg_portal::SessionDetails ScreenCastPortal::GetSessionDetails() {
|
||||
return {}; // No-op
|
||||
}
|
||||
|
||||
void ScreenCastPortal::OnPortalDone(RequestResponse result) {
|
||||
notifier_->OnScreenCastRequestResult(result, pw_stream_node_id_, pw_fd_);
|
||||
if (result != RequestResponse::kSuccess) {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnProxyRequested(GObject* gobject,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
static_cast<ScreenCastPortal*>(user_data)->RequestSessionUsingProxy(result);
|
||||
}
|
||||
|
||||
void ScreenCastPortal::RequestSession(GDBusProxy* proxy) {
|
||||
proxy_ = proxy;
|
||||
connection_ = g_dbus_proxy_get_connection(proxy_);
|
||||
SetupSessionRequestHandlers(
|
||||
"webrtc", OnSessionRequested, OnSessionRequestResponseSignal, connection_,
|
||||
proxy_, cancellable_, portal_handle_, session_request_signal_id_, this);
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnSessionRequested(GDBusProxy* proxy,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
static_cast<ScreenCastPortal*>(user_data)->OnSessionRequestResult(proxy,
|
||||
result);
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnSessionRequestResponseSignal(
|
||||
GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data) {
|
||||
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
||||
RTC_DCHECK(that);
|
||||
that->RegisterSessionClosedSignalHandler(
|
||||
OnSessionClosedSignal, parameters, that->connection_,
|
||||
that->session_handle_, that->session_closed_signal_id_);
|
||||
|
||||
// Do not continue if we don't get session_handle back. The call above will
|
||||
// already notify the capturer there is a failure, but we would still continue
|
||||
// to make following request and crash on that.
|
||||
if (!that->session_handle_.empty()) {
|
||||
that->SourcesRequest();
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnSessionClosedSignal(GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data) {
|
||||
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
RTC_LOG(LS_INFO) << "Received closed signal from session.";
|
||||
|
||||
that->notifier_->OnScreenCastSessionClosed();
|
||||
|
||||
// Unsubscribe from the signal and free the session handle to avoid calling
|
||||
// Session::Close from the destructor since it's already closed
|
||||
g_dbus_connection_signal_unsubscribe(that->connection_,
|
||||
that->session_closed_signal_id_);
|
||||
}
|
||||
|
||||
void ScreenCastPortal::SourcesRequest() {
|
||||
GVariantBuilder builder;
|
||||
Scoped<char> variant_string;
|
||||
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
|
||||
// We want to record monitor content.
|
||||
g_variant_builder_add(
|
||||
&builder, "{sv}", "types",
|
||||
g_variant_new_uint32(static_cast<uint32_t>(capture_source_type_)));
|
||||
// We don't want to allow selection of multiple sources.
|
||||
g_variant_builder_add(&builder, "{sv}", "multiple",
|
||||
g_variant_new_boolean(false));
|
||||
|
||||
Scoped<GVariant> cursorModesVariant(
|
||||
g_dbus_proxy_get_cached_property(proxy_, "AvailableCursorModes"));
|
||||
if (cursorModesVariant.get()) {
|
||||
uint32_t modes = 0;
|
||||
g_variant_get(cursorModesVariant.get(), "u", &modes);
|
||||
// Make request only if this mode is advertised by the portal
|
||||
// implementation.
|
||||
if (modes & static_cast<uint32_t>(cursor_mode_)) {
|
||||
g_variant_builder_add(
|
||||
&builder, "{sv}", "cursor_mode",
|
||||
g_variant_new_uint32(static_cast<uint32_t>(cursor_mode_)));
|
||||
}
|
||||
}
|
||||
|
||||
Scoped<GVariant> versionVariant(
|
||||
g_dbus_proxy_get_cached_property(proxy_, "version"));
|
||||
if (versionVariant.get()) {
|
||||
uint32_t version = 0;
|
||||
g_variant_get(versionVariant.get(), "u", &version);
|
||||
// Make request only if xdg-desktop-portal has required API version
|
||||
if (version >= 4) {
|
||||
g_variant_builder_add(
|
||||
&builder, "{sv}", "persist_mode",
|
||||
g_variant_new_uint32(static_cast<uint32_t>(persist_mode_)));
|
||||
if (!restore_token_.empty()) {
|
||||
g_variant_builder_add(&builder, "{sv}", "restore_token",
|
||||
g_variant_new_string(restore_token_.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
|
||||
g_variant_builder_add(&builder, "{sv}", "handle_token",
|
||||
g_variant_new_string(variant_string.get()));
|
||||
|
||||
sources_handle_ = PrepareSignalHandle(variant_string.get(), connection_);
|
||||
sources_request_signal_id_ = SetupRequestResponseSignal(
|
||||
sources_handle_.c_str(), sources_request_response_signal_handler_,
|
||||
user_data_, connection_);
|
||||
|
||||
RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
|
||||
g_dbus_proxy_call(
|
||||
proxy_, "SelectSources",
|
||||
g_variant_new("(oa{sv})", session_handle_.c_str(), &builder),
|
||||
G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
|
||||
reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this);
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnSourcesRequested(GDBusProxy* proxy,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
Scoped<GError> error;
|
||||
Scoped<GVariant> variant(
|
||||
g_dbus_proxy_call_finish(proxy, result, error.receive()));
|
||||
if (!variant) {
|
||||
if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
return;
|
||||
RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message;
|
||||
that->OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
RTC_LOG(LS_INFO) << "Sources requested from the screen cast session.";
|
||||
|
||||
Scoped<char> handle;
|
||||
g_variant_get_child(variant.get(), 0, "o", handle.receive());
|
||||
if (!handle) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
|
||||
if (that->sources_request_signal_id_) {
|
||||
g_dbus_connection_signal_unsubscribe(that->connection_,
|
||||
that->sources_request_signal_id_);
|
||||
that->sources_request_signal_id_ = 0;
|
||||
}
|
||||
that->OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnSourcesRequestResponseSignal(
|
||||
GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data) {
|
||||
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
RTC_LOG(LS_INFO) << "Received sources signal from session.";
|
||||
|
||||
uint32_t portal_response;
|
||||
g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr);
|
||||
if (portal_response) {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "Failed to select sources for the screen cast session.";
|
||||
that->OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
that->StartRequest();
|
||||
}
|
||||
|
||||
void ScreenCastPortal::StartRequest() {
|
||||
StartSessionRequest("webrtc", session_handle_, OnStartRequestResponseSignal,
|
||||
OnStartRequested, proxy_, connection_, cancellable_,
|
||||
start_request_signal_id_, start_handle_, this);
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnStartRequested(GDBusProxy* proxy,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
static_cast<ScreenCastPortal*>(user_data)->OnStartRequestResult(proxy,
|
||||
result);
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnStartRequestResponseSignal(GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data) {
|
||||
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
RTC_LOG(LS_INFO) << "Start signal received.";
|
||||
uint32_t portal_response;
|
||||
Scoped<GVariant> response_data;
|
||||
Scoped<GVariantIter> iter;
|
||||
Scoped<char> restore_token;
|
||||
g_variant_get(parameters, "(u@a{sv})", &portal_response,
|
||||
response_data.receive());
|
||||
if (portal_response || !response_data) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to start the screen cast session.";
|
||||
that->OnPortalDone(RequestResponseFromPortalResponse(portal_response));
|
||||
return;
|
||||
}
|
||||
|
||||
// Array of PipeWire streams. See
|
||||
// https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.ScreenCast.xml
|
||||
// documentation for <method name="Start">.
|
||||
if (g_variant_lookup(response_data.get(), "streams", "a(ua{sv})",
|
||||
iter.receive())) {
|
||||
Scoped<GVariant> variant;
|
||||
|
||||
while (g_variant_iter_next(iter.get(), "@(ua{sv})", variant.receive())) {
|
||||
uint32_t stream_id;
|
||||
uint32_t type;
|
||||
Scoped<GVariant> options;
|
||||
|
||||
g_variant_get(variant.get(), "(u@a{sv})", &stream_id, options.receive());
|
||||
RTC_DCHECK(options.get());
|
||||
|
||||
if (g_variant_lookup(options.get(), "source_type", "u", &type)) {
|
||||
that->capture_source_type_ =
|
||||
static_cast<ScreenCastPortal::CaptureSourceType>(type);
|
||||
}
|
||||
|
||||
that->pw_stream_node_id_ = stream_id;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_variant_lookup(response_data.get(), "restore_token", "s",
|
||||
restore_token.receive())) {
|
||||
that->restore_token_ = restore_token.get();
|
||||
}
|
||||
|
||||
that->OpenPipeWireRemote();
|
||||
}
|
||||
|
||||
uint32_t ScreenCastPortal::pipewire_stream_node_id() {
|
||||
return pw_stream_node_id_;
|
||||
}
|
||||
|
||||
void ScreenCastPortal::SetPersistMode(ScreenCastPortal::PersistMode mode) {
|
||||
persist_mode_ = mode;
|
||||
}
|
||||
|
||||
void ScreenCastPortal::SetRestoreToken(const std::string& token) {
|
||||
restore_token_ = token;
|
||||
}
|
||||
|
||||
std::string ScreenCastPortal::RestoreToken() const {
|
||||
return restore_token_;
|
||||
}
|
||||
|
||||
void ScreenCastPortal::OpenPipeWireRemote() {
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
|
||||
|
||||
RTC_LOG(LS_INFO) << "Opening the PipeWire remote.";
|
||||
|
||||
g_dbus_proxy_call_with_unix_fd_list(
|
||||
proxy_, "OpenPipeWireRemote",
|
||||
g_variant_new("(oa{sv})", session_handle_.c_str(), &builder),
|
||||
G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr, cancellable_,
|
||||
reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
|
||||
this);
|
||||
}
|
||||
|
||||
// static
|
||||
void ScreenCastPortal::OnOpenPipeWireRemoteRequested(GDBusProxy* proxy,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data) {
|
||||
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
Scoped<GError> error;
|
||||
Scoped<GUnixFDList> outlist;
|
||||
Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish(
|
||||
proxy, outlist.receive(), result, error.receive()));
|
||||
if (!variant) {
|
||||
if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
return;
|
||||
RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: "
|
||||
<< error->message;
|
||||
that->OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t index;
|
||||
g_variant_get(variant.get(), "(h)", &index);
|
||||
|
||||
that->pw_fd_ = g_unix_fd_list_get(outlist.get(), index, error.receive());
|
||||
|
||||
if (that->pw_fd_ == kInvalidPipeWireFd) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
|
||||
<< error->message;
|
||||
that->OnPortalDone(RequestResponse::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
that->OnPortalDone(RequestResponse::kSuccess);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/linux/wayland/screen_capture_portal_interface.h"
|
||||
#include "modules/portal/pipewire_utils.h"
|
||||
#include "modules/portal/portal_request_response.h"
|
||||
#include "modules/portal/xdg_desktop_portal_utils.h"
|
||||
#include "modules/portal/xdg_session_details.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RTC_EXPORT ScreenCastPortal
|
||||
: public xdg_portal::ScreenCapturePortalInterface {
|
||||
public:
|
||||
using ProxyRequestResponseHandler = void (*)(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data);
|
||||
|
||||
using SourcesRequestResponseSignalHandler =
|
||||
void (*)(GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data);
|
||||
|
||||
// Values are set based on cursor mode property in
|
||||
// xdg-desktop-portal/screencast
|
||||
// https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.ScreenCast.xml
|
||||
enum class CursorMode : uint32_t {
|
||||
// Mouse cursor will not be included in any form
|
||||
kHidden = 0b01,
|
||||
// Mouse cursor will be part of the screen content
|
||||
kEmbedded = 0b10,
|
||||
// Mouse cursor information will be send separately in form of metadata
|
||||
kMetadata = 0b100
|
||||
};
|
||||
|
||||
// Values are set based on persist mode property in
|
||||
// xdg-desktop-portal/screencast
|
||||
// https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.ScreenCast.xml
|
||||
enum class PersistMode : uint32_t {
|
||||
// Do not allow to restore stream
|
||||
kDoNotPersist = 0b00,
|
||||
// The restore token is valid as long as the application is alive. It's
|
||||
// stored in memory and revoked when the application closes its DBus
|
||||
// connection
|
||||
kTransient = 0b01,
|
||||
// The restore token is stored in disk and is valid until the user manually
|
||||
// revokes it
|
||||
kPersistent = 0b10
|
||||
};
|
||||
|
||||
// Interface that must be implemented by the ScreenCastPortal consumers.
|
||||
class PortalNotifier {
|
||||
public:
|
||||
virtual void OnScreenCastRequestResult(xdg_portal::RequestResponse result,
|
||||
uint32_t stream_node_id,
|
||||
int fd) = 0;
|
||||
virtual void OnScreenCastSessionClosed() = 0;
|
||||
|
||||
protected:
|
||||
PortalNotifier() = default;
|
||||
virtual ~PortalNotifier() = default;
|
||||
};
|
||||
|
||||
ScreenCastPortal(CaptureType type, PortalNotifier* notifier);
|
||||
ScreenCastPortal(CaptureType type,
|
||||
PortalNotifier* notifier,
|
||||
ProxyRequestResponseHandler proxy_request_response_handler,
|
||||
SourcesRequestResponseSignalHandler
|
||||
sources_request_response_signal_handler,
|
||||
gpointer user_data,
|
||||
// TODO(chromium:1291247): Remove the default option once
|
||||
// downstream has been adjusted.
|
||||
bool prefer_cursor_embedded = false);
|
||||
|
||||
~ScreenCastPortal();
|
||||
|
||||
// Initialize ScreenCastPortal with series of DBus calls where we try to
|
||||
// obtain all the required information, like PipeWire file descriptor and
|
||||
// PipeWire stream node ID.
|
||||
//
|
||||
// The observer will return whether the communication with xdg-desktop-portal
|
||||
// was successful and only then you will be able to get all the required
|
||||
// information in order to continue working with PipeWire.
|
||||
void Start() override;
|
||||
void Stop() override;
|
||||
xdg_portal::SessionDetails GetSessionDetails() override;
|
||||
|
||||
// Method to notify the reason for failure of a portal request.
|
||||
void OnPortalDone(xdg_portal::RequestResponse result) override;
|
||||
|
||||
// Sends a create session request to the portal.
|
||||
void RequestSession(GDBusProxy* proxy) override;
|
||||
|
||||
// Set of methods leveraged by remote desktop portal to setup a common session
|
||||
// with screen cast portal.
|
||||
void SetSessionDetails(const xdg_portal::SessionDetails& session_details);
|
||||
uint32_t pipewire_stream_node_id();
|
||||
void SourcesRequest();
|
||||
void OpenPipeWireRemote();
|
||||
|
||||
// ScreenCast specific methods for stream restoration
|
||||
void SetPersistMode(ScreenCastPortal::PersistMode mode);
|
||||
void SetRestoreToken(const std::string& token);
|
||||
std::string RestoreToken() const;
|
||||
|
||||
private:
|
||||
// Values are set based on source type property in
|
||||
// xdg-desktop-portal/screencast
|
||||
// https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.ScreenCast.xml
|
||||
enum class CaptureSourceType : uint32_t {
|
||||
kScreen = 0b01,
|
||||
kWindow = 0b10,
|
||||
kAnyScreenContent = kScreen | kWindow
|
||||
};
|
||||
static CaptureSourceType ToCaptureSourceType(CaptureType type);
|
||||
|
||||
PortalNotifier* notifier_;
|
||||
|
||||
// A PipeWire stream ID of stream we will be connecting to
|
||||
uint32_t pw_stream_node_id_ = 0;
|
||||
// A file descriptor of PipeWire socket
|
||||
int pw_fd_ = kInvalidPipeWireFd;
|
||||
// Restore token that can be used to restore previous session
|
||||
std::string restore_token_;
|
||||
|
||||
CaptureSourceType capture_source_type_ =
|
||||
ScreenCastPortal::CaptureSourceType::kScreen;
|
||||
|
||||
CursorMode cursor_mode_ = CursorMode::kMetadata;
|
||||
|
||||
PersistMode persist_mode_ = ScreenCastPortal::PersistMode::kDoNotPersist;
|
||||
|
||||
ProxyRequestResponseHandler proxy_request_response_handler_;
|
||||
SourcesRequestResponseSignalHandler sources_request_response_signal_handler_;
|
||||
gpointer user_data_;
|
||||
|
||||
GDBusConnection* connection_ = nullptr;
|
||||
GDBusProxy* proxy_ = nullptr;
|
||||
GCancellable* cancellable_ = nullptr;
|
||||
std::string portal_handle_;
|
||||
std::string session_handle_;
|
||||
std::string sources_handle_;
|
||||
std::string start_handle_;
|
||||
guint session_request_signal_id_ = 0;
|
||||
guint sources_request_signal_id_ = 0;
|
||||
guint start_request_signal_id_ = 0;
|
||||
guint session_closed_signal_id_ = 0;
|
||||
|
||||
void UnsubscribeSignalHandlers();
|
||||
static void OnProxyRequested(GObject* object,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data);
|
||||
static void OnSessionRequested(GDBusProxy* proxy,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data);
|
||||
static void OnSessionRequestResponseSignal(GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data);
|
||||
static void OnSessionClosedSignal(GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data);
|
||||
static void OnSourcesRequested(GDBusProxy* proxy,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data);
|
||||
static void OnSourcesRequestResponseSignal(GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data);
|
||||
|
||||
void StartRequest();
|
||||
static void OnStartRequested(GDBusProxy* proxy,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data);
|
||||
static void OnStartRequestResponseSignal(GDBusConnection* connection,
|
||||
const char* sender_name,
|
||||
const char* object_path,
|
||||
const char* interface_name,
|
||||
const char* signal_name,
|
||||
GVariant* parameters,
|
||||
gpointer user_data);
|
||||
|
||||
static void OnOpenPipeWireRemoteRequested(GDBusProxy* proxy,
|
||||
GAsyncResult* result,
|
||||
gpointer user_data);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_PORTAL_H_
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h"
|
||||
|
||||
#include <libdrm/drm_fourcc.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <spa/param/video/format-utils.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "rtc_base/string_to_number.h"
|
||||
|
||||
#if !PW_CHECK_VERSION(0, 3, 29)
|
||||
#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3)
|
||||
#endif
|
||||
#if !PW_CHECK_VERSION(0, 3, 33)
|
||||
#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4)
|
||||
#endif
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
PipeWireVersion PipeWireVersion::Parse(const absl::string_view& version) {
|
||||
std::vector<absl::string_view> parsed_version = rtc::split(version, '.');
|
||||
|
||||
if (parsed_version.size() != 3) {
|
||||
return {};
|
||||
}
|
||||
|
||||
absl::optional<int> major = rtc::StringToNumber<int>(parsed_version.at(0));
|
||||
absl::optional<int> minor = rtc::StringToNumber<int>(parsed_version.at(1));
|
||||
absl::optional<int> micro = rtc::StringToNumber<int>(parsed_version.at(2));
|
||||
|
||||
// Return invalid version if we failed to parse it
|
||||
if (!major || !minor || !micro) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {major.value(), minor.value(), micro.value()};
|
||||
}
|
||||
|
||||
bool PipeWireVersion::operator>=(const PipeWireVersion& other) {
|
||||
if (!major && !minor && !micro) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::tie(major, minor, micro) >=
|
||||
std::tie(other.major, other.minor, other.micro);
|
||||
}
|
||||
|
||||
bool PipeWireVersion::operator<=(const PipeWireVersion& other) {
|
||||
if (!major && !minor && !micro) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::tie(major, minor, micro) <=
|
||||
std::tie(other.major, other.minor, other.micro);
|
||||
}
|
||||
|
||||
spa_pod* BuildFormat(spa_pod_builder* builder,
|
||||
uint32_t format,
|
||||
const std::vector<uint64_t>& modifiers,
|
||||
const struct spa_rectangle* resolution,
|
||||
const struct spa_fraction* frame_rate) {
|
||||
spa_pod_frame frames[2];
|
||||
spa_rectangle pw_min_screen_bounds = spa_rectangle{1, 1};
|
||||
spa_rectangle pw_max_screen_bounds = spa_rectangle{UINT32_MAX, UINT32_MAX};
|
||||
spa_pod_builder_push_object(builder, &frames[0], SPA_TYPE_OBJECT_Format,
|
||||
SPA_PARAM_EnumFormat);
|
||||
spa_pod_builder_add(builder, SPA_FORMAT_mediaType,
|
||||
SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
|
||||
spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype,
|
||||
SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
|
||||
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
|
||||
|
||||
if (modifiers.size()) {
|
||||
if (modifiers.size() == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
|
||||
spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier,
|
||||
SPA_POD_PROP_FLAG_MANDATORY);
|
||||
spa_pod_builder_long(builder, modifiers[0]);
|
||||
} else {
|
||||
spa_pod_builder_prop(
|
||||
builder, SPA_FORMAT_VIDEO_modifier,
|
||||
SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
|
||||
spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0);
|
||||
|
||||
// modifiers from the array
|
||||
bool first = true;
|
||||
for (int64_t val : modifiers) {
|
||||
spa_pod_builder_long(builder, val);
|
||||
// Add the first modifier twice as the very first value is the default
|
||||
// option
|
||||
if (first) {
|
||||
spa_pod_builder_long(builder, val);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
spa_pod_builder_pop(builder, &frames[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (resolution) {
|
||||
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size,
|
||||
SPA_POD_Rectangle(resolution), 0);
|
||||
} else {
|
||||
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size,
|
||||
SPA_POD_CHOICE_RANGE_Rectangle(&pw_min_screen_bounds,
|
||||
&pw_min_screen_bounds,
|
||||
&pw_max_screen_bounds),
|
||||
0);
|
||||
}
|
||||
if (frame_rate) {
|
||||
static const spa_fraction pw_min_frame_rate = spa_fraction{0, 1};
|
||||
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate,
|
||||
SPA_POD_CHOICE_RANGE_Fraction(
|
||||
frame_rate, &pw_min_frame_rate, frame_rate),
|
||||
0);
|
||||
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_maxFramerate,
|
||||
SPA_POD_CHOICE_RANGE_Fraction(
|
||||
frame_rate, &pw_min_frame_rate, frame_rate),
|
||||
0);
|
||||
}
|
||||
return static_cast<spa_pod*>(spa_pod_builder_pop(builder, &frames[0]));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rtc_base/string_encode.h"
|
||||
|
||||
struct spa_pod;
|
||||
struct spa_pod_builder;
|
||||
struct spa_rectangle;
|
||||
struct spa_fraction;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
struct PipeWireVersion {
|
||||
static PipeWireVersion Parse(const absl::string_view& version);
|
||||
|
||||
// Returns whether current version is newer or same as required version
|
||||
bool operator>=(const PipeWireVersion& other);
|
||||
// Returns whether current version is older or same as required version
|
||||
bool operator<=(const PipeWireVersion& other);
|
||||
|
||||
int major = 0;
|
||||
int minor = 0;
|
||||
int micro = 0;
|
||||
};
|
||||
|
||||
// Returns a spa_pod used to build PipeWire stream format using given
|
||||
// arguments. Modifiers are optional value and when present they will be
|
||||
// used with SPA_POD_PROP_FLAG_MANDATORY and SPA_POD_PROP_FLAG_DONT_FIXATE
|
||||
// flags.
|
||||
spa_pod* BuildFormat(spa_pod_builder* builder,
|
||||
uint32_t format,
|
||||
const std::vector<uint64_t>& modifiers,
|
||||
const struct spa_rectangle* resolution,
|
||||
const struct spa_fraction* frame_rate);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SCREENCAST_STREAM_UTILS_H_
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/ref_counted_base.h"
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/mouse_cursor.h"
|
||||
#include "modules/desktop_capture/screen_capture_frame_queue.h"
|
||||
#include "modules/desktop_capture/shared_desktop_frame.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class SharedScreenCastStreamPrivate;
|
||||
|
||||
class RTC_EXPORT SharedScreenCastStream
|
||||
: public rtc::RefCountedNonVirtual<SharedScreenCastStream> {
|
||||
public:
|
||||
class Observer {
|
||||
public:
|
||||
virtual void OnCursorPositionChanged() = 0;
|
||||
virtual void OnCursorShapeChanged() = 0;
|
||||
virtual void OnDesktopFrameChanged() = 0;
|
||||
virtual void OnFailedToProcessBuffer() = 0;
|
||||
virtual void OnStreamConfigured() = 0;
|
||||
virtual void OnFrameRateChanged(uint32_t frame_rate) = 0;
|
||||
|
||||
protected:
|
||||
Observer() = default;
|
||||
virtual ~Observer() = default;
|
||||
};
|
||||
|
||||
static rtc::scoped_refptr<SharedScreenCastStream> CreateDefault();
|
||||
|
||||
bool StartScreenCastStream(uint32_t stream_node_id);
|
||||
bool StartScreenCastStream(uint32_t stream_node_id,
|
||||
int fd,
|
||||
uint32_t width = 0,
|
||||
uint32_t height = 0,
|
||||
bool is_cursor_embedded = false,
|
||||
DesktopCapturer::Callback* callback = nullptr);
|
||||
void UpdateScreenCastStreamResolution(uint32_t width, uint32_t height);
|
||||
void UpdateScreenCastStreamFrameRate(uint32_t frame_rate);
|
||||
void SetUseDamageRegion(bool use_damage_region);
|
||||
void SetObserver(SharedScreenCastStream::Observer* observer);
|
||||
void StopScreenCastStream();
|
||||
|
||||
// Below functions return the most recent information we get from a
|
||||
// PipeWire buffer on each Process() callback. This assumes that we
|
||||
// managed to successfuly connect to a PipeWire stream provided by the
|
||||
// compositor (based on stream parameters). The cursor data are obtained
|
||||
// from spa_meta_cursor stream metadata and therefore the cursor is not
|
||||
// part of actual screen/window frame.
|
||||
|
||||
// Returns the most recent screen/window frame we obtained from PipeWire
|
||||
// buffer. Will return an empty frame in case we didn't manage to get a frame
|
||||
// from PipeWire buffer.
|
||||
std::unique_ptr<SharedDesktopFrame> CaptureFrame();
|
||||
|
||||
// Returns the most recent mouse cursor image. Will return an nullptr cursor
|
||||
// in case we didn't manage to get a cursor from PipeWire buffer. NOTE: the
|
||||
// cursor image might not be updated on every cursor location change, but
|
||||
// actually only when its shape changes.
|
||||
std::unique_ptr<MouseCursor> CaptureCursor();
|
||||
|
||||
// Returns the most recent mouse cursor position. Will not return a value in
|
||||
// case we didn't manage to get it from PipeWire buffer.
|
||||
absl::optional<DesktopVector> CaptureCursorPosition();
|
||||
|
||||
~SharedScreenCastStream();
|
||||
|
||||
protected:
|
||||
SharedScreenCastStream();
|
||||
|
||||
private:
|
||||
friend class SharedScreenCastStreamPrivate;
|
||||
|
||||
SharedScreenCastStream(const SharedScreenCastStream&) = delete;
|
||||
SharedScreenCastStream& operator=(const SharedScreenCastStream&) = delete;
|
||||
|
||||
std::unique_ptr<SharedScreenCastStreamPrivate> private_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_SHARED_SCREENCAST_STREAM_H_
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
|
||||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/wayland/test/test_screencast_stream_provider.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/portal/pipewire_utils.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
constexpr int kBytesPerPixel = 4;
|
||||
|
||||
TestScreenCastStreamProvider::TestScreenCastStreamProvider(Observer* observer,
|
||||
uint32_t width,
|
||||
uint32_t height)
|
||||
: observer_(observer), width_(width), height_(height) {
|
||||
if (!InitializePipeWire()) {
|
||||
RTC_LOG(LS_ERROR) << "Unable to open PipeWire";
|
||||
return;
|
||||
}
|
||||
|
||||
pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
|
||||
|
||||
pw_main_loop_ = pw_thread_loop_new("pipewire-test-main-loop", nullptr);
|
||||
|
||||
pw_context_ =
|
||||
pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0);
|
||||
if (!pw_context_) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to create PipeWire context";
|
||||
return;
|
||||
}
|
||||
|
||||
if (pw_thread_loop_start(pw_main_loop_) < 0) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to start main PipeWire loop";
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize event handlers, remote end and stream-related.
|
||||
pw_core_events_.version = PW_VERSION_CORE_EVENTS;
|
||||
pw_core_events_.error = &OnCoreError;
|
||||
|
||||
pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
|
||||
pw_stream_events_.add_buffer = &OnStreamAddBuffer;
|
||||
pw_stream_events_.remove_buffer = &OnStreamRemoveBuffer;
|
||||
pw_stream_events_.state_changed = &OnStreamStateChanged;
|
||||
pw_stream_events_.param_changed = &OnStreamParamChanged;
|
||||
|
||||
{
|
||||
PipeWireThreadLoopLock thread_loop_lock(pw_main_loop_);
|
||||
|
||||
pw_core_ = pw_context_connect(pw_context_, nullptr, 0);
|
||||
if (!pw_core_) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to connect PipeWire context";
|
||||
return;
|
||||
}
|
||||
|
||||
pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this);
|
||||
|
||||
pw_stream_ = pw_stream_new(pw_core_, "webrtc-test-stream", nullptr);
|
||||
|
||||
if (!pw_stream_) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to create PipeWire stream";
|
||||
return;
|
||||
}
|
||||
|
||||
pw_stream_add_listener(pw_stream_, &spa_stream_listener_,
|
||||
&pw_stream_events_, this);
|
||||
uint8_t buffer[2048] = {};
|
||||
|
||||
spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
|
||||
|
||||
std::vector<const spa_pod*> params;
|
||||
|
||||
spa_rectangle resolution =
|
||||
SPA_RECTANGLE(uint32_t(width_), uint32_t(height_));
|
||||
struct spa_fraction default_frame_rate = SPA_FRACTION(60, 1);
|
||||
params.push_back(BuildFormat(&builder, SPA_VIDEO_FORMAT_BGRx,
|
||||
/*modifiers=*/{}, &resolution,
|
||||
&default_frame_rate));
|
||||
|
||||
auto flags =
|
||||
pw_stream_flags(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS);
|
||||
if (pw_stream_connect(pw_stream_, PW_DIRECTION_OUTPUT, SPA_ID_INVALID,
|
||||
flags, params.data(), params.size()) != 0) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Could not connect receiving stream.";
|
||||
pw_stream_destroy(pw_stream_);
|
||||
pw_stream_ = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TestScreenCastStreamProvider::~TestScreenCastStreamProvider() {
|
||||
if (pw_main_loop_) {
|
||||
pw_thread_loop_stop(pw_main_loop_);
|
||||
}
|
||||
|
||||
if (pw_stream_) {
|
||||
pw_stream_destroy(pw_stream_);
|
||||
}
|
||||
|
||||
if (pw_core_) {
|
||||
pw_core_disconnect(pw_core_);
|
||||
}
|
||||
|
||||
if (pw_context_) {
|
||||
pw_context_destroy(pw_context_);
|
||||
}
|
||||
|
||||
if (pw_main_loop_) {
|
||||
pw_thread_loop_destroy(pw_main_loop_);
|
||||
}
|
||||
}
|
||||
|
||||
void TestScreenCastStreamProvider::RecordFrame(RgbaColor rgba_color) {
|
||||
const char* error;
|
||||
if (pw_stream_get_state(pw_stream_, &error) != PW_STREAM_STATE_STREAMING) {
|
||||
if (error) {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "PipeWire test: Failed to record frame: stream is not active: "
|
||||
<< error;
|
||||
}
|
||||
}
|
||||
|
||||
struct pw_buffer* buffer = pw_stream_dequeue_buffer(pw_stream_);
|
||||
if (!buffer) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: No available buffer";
|
||||
return;
|
||||
}
|
||||
|
||||
struct spa_buffer* spa_buffer = buffer->buffer;
|
||||
struct spa_data* spa_data = spa_buffer->datas;
|
||||
uint8_t* data = static_cast<uint8_t*>(spa_data->data);
|
||||
if (!data) {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "PipeWire test: Failed to record frame: invalid buffer data";
|
||||
pw_stream_queue_buffer(pw_stream_, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
const int stride = SPA_ROUND_UP_N(width_ * kBytesPerPixel, 4);
|
||||
|
||||
spa_data->chunk->offset = 0;
|
||||
spa_data->chunk->size = height_ * stride;
|
||||
spa_data->chunk->stride = stride;
|
||||
|
||||
uint32_t color = rgba_color.ToUInt32();
|
||||
for (uint32_t i = 0; i < height_; i++) {
|
||||
uint32_t* column = reinterpret_cast<uint32_t*>(data);
|
||||
for (uint32_t j = 0; j < width_; j++) {
|
||||
column[j] = color;
|
||||
}
|
||||
data += stride;
|
||||
}
|
||||
|
||||
pw_stream_queue_buffer(pw_stream_, buffer);
|
||||
if (observer_) {
|
||||
observer_->OnFrameRecorded();
|
||||
}
|
||||
}
|
||||
|
||||
void TestScreenCastStreamProvider::StartStreaming() {
|
||||
if (pw_stream_ && pw_node_id_ != 0) {
|
||||
pw_stream_set_active(pw_stream_, true);
|
||||
}
|
||||
}
|
||||
|
||||
void TestScreenCastStreamProvider::StopStreaming() {
|
||||
if (pw_stream_ && pw_node_id_ != 0) {
|
||||
pw_stream_set_active(pw_stream_, false);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void TestScreenCastStreamProvider::OnCoreError(void* data,
|
||||
uint32_t id,
|
||||
int seq,
|
||||
int res,
|
||||
const char* message) {
|
||||
TestScreenCastStreamProvider* that =
|
||||
static_cast<TestScreenCastStreamProvider*>(data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: PipeWire remote error: " << message;
|
||||
}
|
||||
|
||||
// static
|
||||
void TestScreenCastStreamProvider::OnStreamStateChanged(
|
||||
void* data,
|
||||
pw_stream_state old_state,
|
||||
pw_stream_state state,
|
||||
const char* error_message) {
|
||||
TestScreenCastStreamProvider* that =
|
||||
static_cast<TestScreenCastStreamProvider*>(data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
switch (state) {
|
||||
case PW_STREAM_STATE_ERROR:
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: PipeWire stream state error: "
|
||||
<< error_message;
|
||||
break;
|
||||
case PW_STREAM_STATE_PAUSED:
|
||||
if (that->pw_node_id_ == 0 && that->pw_stream_) {
|
||||
that->pw_node_id_ = pw_stream_get_node_id(that->pw_stream_);
|
||||
that->observer_->OnStreamReady(that->pw_node_id_);
|
||||
} else {
|
||||
// Stop streaming
|
||||
that->is_streaming_ = false;
|
||||
that->observer_->OnStopStreaming();
|
||||
}
|
||||
break;
|
||||
case PW_STREAM_STATE_STREAMING:
|
||||
// Start streaming
|
||||
that->is_streaming_ = true;
|
||||
that->observer_->OnStartStreaming();
|
||||
break;
|
||||
case PW_STREAM_STATE_CONNECTING:
|
||||
break;
|
||||
case PW_STREAM_STATE_UNCONNECTED:
|
||||
if (that->is_streaming_) {
|
||||
// Stop streaming
|
||||
that->is_streaming_ = false;
|
||||
that->observer_->OnStopStreaming();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void TestScreenCastStreamProvider::OnStreamParamChanged(
|
||||
void* data,
|
||||
uint32_t id,
|
||||
const struct spa_pod* format) {
|
||||
TestScreenCastStreamProvider* that =
|
||||
static_cast<TestScreenCastStreamProvider*>(data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
RTC_LOG(LS_INFO) << "PipeWire test: PipeWire stream format changed.";
|
||||
if (!format || id != SPA_PARAM_Format) {
|
||||
return;
|
||||
}
|
||||
|
||||
spa_format_video_raw_parse(format, &that->spa_video_format_);
|
||||
|
||||
auto stride = SPA_ROUND_UP_N(that->width_ * kBytesPerPixel, 4);
|
||||
|
||||
uint8_t buffer[1024] = {};
|
||||
auto builder = spa_pod_builder{buffer, sizeof(buffer)};
|
||||
|
||||
// Setup buffers and meta header for new format.
|
||||
|
||||
std::vector<const spa_pod*> params;
|
||||
const int buffer_types = (1 << SPA_DATA_MemFd);
|
||||
spa_rectangle resolution = SPA_RECTANGLE(that->width_, that->height_);
|
||||
|
||||
params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
|
||||
&builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&resolution),
|
||||
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16),
|
||||
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_stride,
|
||||
SPA_POD_Int(stride), SPA_PARAM_BUFFERS_size,
|
||||
SPA_POD_Int(stride * that->height_), SPA_PARAM_BUFFERS_align,
|
||||
SPA_POD_Int(16), SPA_PARAM_BUFFERS_dataType,
|
||||
SPA_POD_CHOICE_FLAGS_Int(buffer_types))));
|
||||
params.push_back(reinterpret_cast<spa_pod*>(spa_pod_builder_add_object(
|
||||
&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
|
||||
SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size,
|
||||
SPA_POD_Int(sizeof(struct spa_meta_header)))));
|
||||
|
||||
pw_stream_update_params(that->pw_stream_, params.data(), params.size());
|
||||
}
|
||||
|
||||
// static
|
||||
void TestScreenCastStreamProvider::OnStreamAddBuffer(void* data,
|
||||
pw_buffer* buffer) {
|
||||
TestScreenCastStreamProvider* that =
|
||||
static_cast<TestScreenCastStreamProvider*>(data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
struct spa_data* spa_data = buffer->buffer->datas;
|
||||
|
||||
spa_data->mapoffset = 0;
|
||||
spa_data->flags = SPA_DATA_FLAG_READWRITE;
|
||||
|
||||
if (!(spa_data[0].type & (1 << SPA_DATA_MemFd))) {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "PipeWire test: Client doesn't support memfd buffer data type";
|
||||
return;
|
||||
}
|
||||
|
||||
const int stride = SPA_ROUND_UP_N(that->width_ * kBytesPerPixel, 4);
|
||||
spa_data->maxsize = stride * that->height_;
|
||||
spa_data->type = SPA_DATA_MemFd;
|
||||
spa_data->fd =
|
||||
memfd_create("pipewire-test-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
if (spa_data->fd == kInvalidPipeWireFd) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Can't create memfd";
|
||||
return;
|
||||
}
|
||||
|
||||
spa_data->mapoffset = 0;
|
||||
|
||||
if (ftruncate(spa_data->fd, spa_data->maxsize) < 0) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Can't truncate to"
|
||||
<< spa_data->maxsize;
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
|
||||
if (fcntl(spa_data->fd, F_ADD_SEALS, seals) == -1) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to add seals";
|
||||
}
|
||||
|
||||
spa_data->data = mmap(nullptr, spa_data->maxsize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, spa_data->fd, spa_data->mapoffset);
|
||||
if (spa_data->data == MAP_FAILED) {
|
||||
RTC_LOG(LS_ERROR) << "PipeWire test: Failed to mmap memory";
|
||||
} else {
|
||||
that->observer_->OnBufferAdded();
|
||||
RTC_LOG(LS_INFO) << "PipeWire test: Memfd created successfully: "
|
||||
<< spa_data->data << spa_data->maxsize;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void TestScreenCastStreamProvider::OnStreamRemoveBuffer(void* data,
|
||||
pw_buffer* buffer) {
|
||||
TestScreenCastStreamProvider* that =
|
||||
static_cast<TestScreenCastStreamProvider*>(data);
|
||||
RTC_DCHECK(that);
|
||||
|
||||
struct spa_buffer* spa_buffer = buffer->buffer;
|
||||
struct spa_data* spa_data = spa_buffer->datas;
|
||||
if (spa_data && spa_data->type == SPA_DATA_MemFd) {
|
||||
munmap(spa_data->data, spa_data->maxsize);
|
||||
close(spa_data->fd);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t TestScreenCastStreamProvider::PipeWireNodeId() {
|
||||
return pw_node_id_;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_TEST_SCREENCAST_STREAM_PROVIDER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_TEST_SCREENCAST_STREAM_PROVIDER_H_
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <spa/param/video/format-utils.h>
|
||||
|
||||
#include "modules/desktop_capture/linux/wayland/screencast_stream_utils.h"
|
||||
#include "modules/desktop_capture/rgba_color.h"
|
||||
#include "rtc_base/random.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class TestScreenCastStreamProvider {
|
||||
public:
|
||||
class Observer {
|
||||
public:
|
||||
virtual void OnBufferAdded() = 0;
|
||||
virtual void OnFrameRecorded() = 0;
|
||||
virtual void OnStreamReady(uint32_t stream_node_id) = 0;
|
||||
virtual void OnStartStreaming() = 0;
|
||||
virtual void OnStopStreaming() = 0;
|
||||
|
||||
protected:
|
||||
Observer() = default;
|
||||
virtual ~Observer() = default;
|
||||
};
|
||||
|
||||
explicit TestScreenCastStreamProvider(Observer* observer,
|
||||
uint32_t width,
|
||||
uint32_t height);
|
||||
~TestScreenCastStreamProvider();
|
||||
|
||||
uint32_t PipeWireNodeId();
|
||||
|
||||
void RecordFrame(RgbaColor rgba_color);
|
||||
void StartStreaming();
|
||||
void StopStreaming();
|
||||
|
||||
private:
|
||||
Observer* observer_;
|
||||
|
||||
// Resolution parameters.
|
||||
uint32_t width_ = 0;
|
||||
uint32_t height_ = 0;
|
||||
|
||||
bool is_streaming_ = false;
|
||||
uint32_t pw_node_id_ = 0;
|
||||
|
||||
// PipeWire types
|
||||
struct pw_context* pw_context_ = nullptr;
|
||||
struct pw_core* pw_core_ = nullptr;
|
||||
struct pw_stream* pw_stream_ = nullptr;
|
||||
struct pw_thread_loop* pw_main_loop_ = nullptr;
|
||||
|
||||
spa_hook spa_core_listener_;
|
||||
spa_hook spa_stream_listener_;
|
||||
|
||||
// event handlers
|
||||
pw_core_events pw_core_events_ = {};
|
||||
pw_stream_events pw_stream_events_ = {};
|
||||
|
||||
struct spa_video_info_raw spa_video_format_;
|
||||
|
||||
// PipeWire callbacks
|
||||
static void OnCoreError(void* data,
|
||||
uint32_t id,
|
||||
int seq,
|
||||
int res,
|
||||
const char* message);
|
||||
static void OnStreamAddBuffer(void* data, pw_buffer* buffer);
|
||||
static void OnStreamRemoveBuffer(void* data, pw_buffer* buffer);
|
||||
static void OnStreamParamChanged(void* data,
|
||||
uint32_t id,
|
||||
const struct spa_pod* format);
|
||||
static void OnStreamStateChanged(void* data,
|
||||
pw_stream_state old_state,
|
||||
pw_stream_state state,
|
||||
const char* error_message);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_TEST_TEST_SCREENCAST_STREAM_PROVIDER_H_
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_
|
||||
|
||||
// TODO(bugs.webrtc.org/14187): remove when all users are gone
|
||||
#include "modules/portal/xdg_desktop_portal_utils.h"
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_
|
||||
|
||||
// TODO(bugs.webrtc.org/14187): remove when all users are gone
|
||||
#include "modules/portal/xdg_session_details.h"
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_SESSION_DETAILS_H_
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/extensions/xfixeswire.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_error_trap.h"
|
||||
#include "modules/desktop_capture/mouse_cursor.h"
|
||||
#include "modules/desktop_capture/mouse_cursor_monitor.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// WindowCapturer returns window IDs of X11 windows with WM_STATE attribute.
|
||||
// These windows may not be immediate children of the root window, because
|
||||
// window managers may re-parent them to add decorations. However,
|
||||
// XQueryPointer() expects to be passed children of the root. This function
|
||||
// searches up the list of the windows to find the root child that corresponds
|
||||
// to `window`.
|
||||
Window GetTopLevelWindow(Display* display, Window window) {
|
||||
while (true) {
|
||||
// If the window is in WithdrawnState then look at all of its children.
|
||||
::Window root, parent;
|
||||
::Window* children;
|
||||
unsigned int num_children;
|
||||
if (!XQueryTree(display, window, &root, &parent, &children,
|
||||
&num_children)) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to query for child windows although window"
|
||||
"does not have a valid WM_STATE.";
|
||||
return None;
|
||||
}
|
||||
if (children)
|
||||
XFree(children);
|
||||
|
||||
if (parent == root)
|
||||
break;
|
||||
|
||||
window = parent;
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
MouseCursorMonitorX11::MouseCursorMonitorX11(
|
||||
const DesktopCaptureOptions& options,
|
||||
Window window)
|
||||
: x_display_(options.x_display()),
|
||||
callback_(NULL),
|
||||
mode_(SHAPE_AND_POSITION),
|
||||
window_(window),
|
||||
have_xfixes_(false),
|
||||
xfixes_event_base_(-1),
|
||||
xfixes_error_base_(-1) {
|
||||
// Set a default initial cursor shape in case XFixes is not present.
|
||||
const int kSize = 5;
|
||||
std::unique_ptr<DesktopFrame> default_cursor(
|
||||
new BasicDesktopFrame(DesktopSize(kSize, kSize)));
|
||||
const uint8_t pixels[kSize * kSize] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
|
||||
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t* ptr = default_cursor->data();
|
||||
for (int y = 0; y < kSize; ++y) {
|
||||
for (int x = 0; x < kSize; ++x) {
|
||||
*ptr++ = pixels[kSize * y + x];
|
||||
*ptr++ = pixels[kSize * y + x];
|
||||
*ptr++ = pixels[kSize * y + x];
|
||||
*ptr++ = 0xff;
|
||||
}
|
||||
}
|
||||
DesktopVector hotspot(2, 2);
|
||||
cursor_shape_.reset(new MouseCursor(default_cursor.release(), hotspot));
|
||||
}
|
||||
|
||||
MouseCursorMonitorX11::~MouseCursorMonitorX11() {
|
||||
if (have_xfixes_) {
|
||||
x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify,
|
||||
this);
|
||||
}
|
||||
}
|
||||
|
||||
void MouseCursorMonitorX11::Init(Callback* callback, Mode mode) {
|
||||
// Init can be called only once per instance of MouseCursorMonitor.
|
||||
RTC_DCHECK(!callback_);
|
||||
RTC_DCHECK(callback);
|
||||
|
||||
callback_ = callback;
|
||||
mode_ = mode;
|
||||
|
||||
have_xfixes_ =
|
||||
XFixesQueryExtension(display(), &xfixes_event_base_, &xfixes_error_base_);
|
||||
|
||||
if (have_xfixes_) {
|
||||
// Register for changes to the cursor shape.
|
||||
XFixesSelectCursorInput(display(), window_, XFixesDisplayCursorNotifyMask);
|
||||
x_display_->AddEventHandler(xfixes_event_base_ + XFixesCursorNotify, this);
|
||||
|
||||
CaptureCursor();
|
||||
} else {
|
||||
RTC_LOG(LS_INFO) << "X server does not support XFixes.";
|
||||
}
|
||||
}
|
||||
|
||||
void MouseCursorMonitorX11::Capture() {
|
||||
RTC_DCHECK(callback_);
|
||||
|
||||
// Process X11 events in case XFixes has sent cursor notification.
|
||||
x_display_->ProcessPendingXEvents();
|
||||
|
||||
// cursor_shape_| is set only if we were notified of a cursor shape change.
|
||||
if (cursor_shape_.get())
|
||||
callback_->OnMouseCursor(cursor_shape_.release());
|
||||
|
||||
// Get cursor position if necessary.
|
||||
if (mode_ == SHAPE_AND_POSITION) {
|
||||
int root_x;
|
||||
int root_y;
|
||||
int win_x;
|
||||
int win_y;
|
||||
Window root_window;
|
||||
Window child_window;
|
||||
unsigned int mask;
|
||||
|
||||
CursorState state;
|
||||
{
|
||||
XErrorTrap error_trap(display());
|
||||
Bool result =
|
||||
XQueryPointer(display(), window_, &root_window, &child_window,
|
||||
&root_x, &root_y, &win_x, &win_y, &mask);
|
||||
if (!result || error_trap.GetLastErrorAndDisable() != 0) {
|
||||
state = OUTSIDE;
|
||||
} else {
|
||||
// In screen mode (window_ == root_window) the mouse is always inside.
|
||||
// XQueryPointer() sets `child_window` to None if the cursor is outside
|
||||
// `window_`.
|
||||
state =
|
||||
(window_ == root_window || child_window != None) ? INSIDE : OUTSIDE;
|
||||
}
|
||||
}
|
||||
|
||||
// As the comments to GetTopLevelWindow() above indicate, in window capture,
|
||||
// the cursor position capture happens in `window_`, while the frame catpure
|
||||
// happens in `child_window`. These two windows are not alwyas same, as
|
||||
// window manager may add some decorations to the `window_`. So translate
|
||||
// the coordinate in `window_` to the coordinate space of `child_window`.
|
||||
if (window_ != root_window && state == INSIDE) {
|
||||
int translated_x, translated_y;
|
||||
Window unused;
|
||||
if (XTranslateCoordinates(display(), window_, child_window, win_x, win_y,
|
||||
&translated_x, &translated_y, &unused)) {
|
||||
win_x = translated_x;
|
||||
win_y = translated_y;
|
||||
}
|
||||
}
|
||||
|
||||
// X11 always starts the coordinate from (0, 0), so we do not need to
|
||||
// translate here.
|
||||
callback_->OnMouseCursorPosition(DesktopVector(root_x, root_y));
|
||||
}
|
||||
}
|
||||
|
||||
bool MouseCursorMonitorX11::HandleXEvent(const XEvent& event) {
|
||||
if (have_xfixes_ && event.type == xfixes_event_base_ + XFixesCursorNotify) {
|
||||
const XFixesCursorNotifyEvent* cursor_event =
|
||||
reinterpret_cast<const XFixesCursorNotifyEvent*>(&event);
|
||||
if (cursor_event->subtype == XFixesDisplayCursorNotify) {
|
||||
CaptureCursor();
|
||||
}
|
||||
// Return false, even if the event has been handled, because there might be
|
||||
// other listeners for cursor notifications.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MouseCursorMonitorX11::CaptureCursor() {
|
||||
RTC_DCHECK(have_xfixes_);
|
||||
|
||||
XFixesCursorImage* img;
|
||||
{
|
||||
XErrorTrap error_trap(display());
|
||||
img = XFixesGetCursorImage(display());
|
||||
if (!img || error_trap.GetLastErrorAndDisable() != 0)
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> image(
|
||||
new BasicDesktopFrame(DesktopSize(img->width, img->height)));
|
||||
|
||||
// Xlib stores 32-bit data in longs, even if longs are 64-bits long.
|
||||
unsigned long* src = img->pixels; // NOLINT(runtime/int)
|
||||
uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
|
||||
uint32_t* dst_end = dst + (img->width * img->height);
|
||||
while (dst < dst_end) {
|
||||
*dst++ = static_cast<uint32_t>(*src++);
|
||||
}
|
||||
|
||||
DesktopVector hotspot(std::min(img->width, img->xhot),
|
||||
std::min(img->height, img->yhot));
|
||||
|
||||
XFree(img);
|
||||
|
||||
cursor_shape_.reset(new MouseCursor(image.release(), hotspot));
|
||||
}
|
||||
|
||||
// static
|
||||
MouseCursorMonitor* MouseCursorMonitorX11::CreateForWindow(
|
||||
const DesktopCaptureOptions& options,
|
||||
WindowId window) {
|
||||
if (!options.x_display())
|
||||
return NULL;
|
||||
window = GetTopLevelWindow(options.x_display()->display(), window);
|
||||
if (window == None)
|
||||
return NULL;
|
||||
return new MouseCursorMonitorX11(options, window);
|
||||
}
|
||||
|
||||
MouseCursorMonitor* MouseCursorMonitorX11::CreateForScreen(
|
||||
const DesktopCaptureOptions& options,
|
||||
ScreenId screen) {
|
||||
if (!options.x_display())
|
||||
return NULL;
|
||||
return new MouseCursorMonitorX11(
|
||||
options, DefaultRootWindow(options.x_display()->display()));
|
||||
}
|
||||
|
||||
std::unique_ptr<MouseCursorMonitor> MouseCursorMonitorX11::Create(
|
||||
const DesktopCaptureOptions& options) {
|
||||
return std::unique_ptr<MouseCursorMonitor>(
|
||||
CreateForScreen(options, kFullDesktopScreenId));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_MOUSE_CURSOR_MONITOR_X11_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_MOUSE_CURSOR_MONITOR_X11_H_
|
||||
|
||||
#include <X11/X.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/linux/x11/shared_x_display.h"
|
||||
#include "modules/desktop_capture/mouse_cursor.h"
|
||||
#include "modules/desktop_capture/mouse_cursor_monitor.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class MouseCursorMonitorX11 : public MouseCursorMonitor,
|
||||
public SharedXDisplay::XEventHandler {
|
||||
public:
|
||||
MouseCursorMonitorX11(const DesktopCaptureOptions& options, Window window);
|
||||
~MouseCursorMonitorX11() override;
|
||||
|
||||
static MouseCursorMonitor* CreateForWindow(
|
||||
const DesktopCaptureOptions& options,
|
||||
WindowId window);
|
||||
static MouseCursorMonitor* CreateForScreen(
|
||||
const DesktopCaptureOptions& options,
|
||||
ScreenId screen);
|
||||
static std::unique_ptr<MouseCursorMonitor> Create(
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
void Init(Callback* callback, Mode mode) override;
|
||||
void Capture() override;
|
||||
|
||||
private:
|
||||
// SharedXDisplay::XEventHandler interface.
|
||||
bool HandleXEvent(const XEvent& event) override;
|
||||
|
||||
Display* display() { return x_display_->display(); }
|
||||
|
||||
// Captures current cursor shape and stores it in `cursor_shape_`.
|
||||
void CaptureCursor();
|
||||
|
||||
rtc::scoped_refptr<SharedXDisplay> x_display_;
|
||||
Callback* callback_;
|
||||
Mode mode_;
|
||||
Window window_;
|
||||
|
||||
bool have_xfixes_;
|
||||
int xfixes_event_base_;
|
||||
int xfixes_error_base_;
|
||||
|
||||
std::unique_ptr<MouseCursor> cursor_shape_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_MOUSE_CURSOR_MONITOR_X11_H_
|
||||
|
|
@ -0,0 +1,517 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/screen_capturer_x11.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/Xdamage.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/extensions/damagewire.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h"
|
||||
#include "modules/desktop_capture/screen_capture_frame_queue.h"
|
||||
#include "modules/desktop_capture/screen_capturer_helper.h"
|
||||
#include "modules/desktop_capture/shared_desktop_frame.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/sanitizer.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
#include "rtc_base/trace_event.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
ScreenCapturerX11::ScreenCapturerX11() {
|
||||
helper_.SetLogGridSize(4);
|
||||
}
|
||||
|
||||
ScreenCapturerX11::~ScreenCapturerX11() {
|
||||
options_.x_display()->RemoveEventHandler(ConfigureNotify, this);
|
||||
if (use_damage_) {
|
||||
options_.x_display()->RemoveEventHandler(damage_event_base_ + XDamageNotify,
|
||||
this);
|
||||
}
|
||||
if (use_randr_) {
|
||||
options_.x_display()->RemoveEventHandler(
|
||||
randr_event_base_ + RRScreenChangeNotify, this);
|
||||
}
|
||||
DeinitXlib();
|
||||
}
|
||||
|
||||
bool ScreenCapturerX11::Init(const DesktopCaptureOptions& options) {
|
||||
TRACE_EVENT0("webrtc", "ScreenCapturerX11::Init");
|
||||
options_ = options;
|
||||
|
||||
atom_cache_ = std::make_unique<XAtomCache>(display());
|
||||
|
||||
root_window_ = RootWindow(display(), DefaultScreen(display()));
|
||||
if (root_window_ == BadValue) {
|
||||
RTC_LOG(LS_ERROR) << "Unable to get the root window";
|
||||
DeinitXlib();
|
||||
return false;
|
||||
}
|
||||
|
||||
gc_ = XCreateGC(display(), root_window_, 0, NULL);
|
||||
if (gc_ == NULL) {
|
||||
RTC_LOG(LS_ERROR) << "Unable to get graphics context";
|
||||
DeinitXlib();
|
||||
return false;
|
||||
}
|
||||
|
||||
options_.x_display()->AddEventHandler(ConfigureNotify, this);
|
||||
|
||||
// Check for XFixes extension. This is required for cursor shape
|
||||
// notifications, and for our use of XDamage.
|
||||
if (XFixesQueryExtension(display(), &xfixes_event_base_,
|
||||
&xfixes_error_base_)) {
|
||||
has_xfixes_ = true;
|
||||
} else {
|
||||
RTC_LOG(LS_INFO) << "X server does not support XFixes.";
|
||||
}
|
||||
|
||||
// Register for changes to the dimensions of the root window.
|
||||
XSelectInput(display(), root_window_, StructureNotifyMask);
|
||||
|
||||
if (!x_server_pixel_buffer_.Init(atom_cache_.get(),
|
||||
DefaultRootWindow(display()))) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options_.use_update_notifications()) {
|
||||
InitXDamage();
|
||||
}
|
||||
|
||||
InitXrandr();
|
||||
|
||||
// Default source set here so that selected_monitor_rect_ is sized correctly.
|
||||
SelectSource(kFullDesktopScreenId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenCapturerX11::InitXDamage() {
|
||||
// Our use of XDamage requires XFixes.
|
||||
if (!has_xfixes_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for XDamage extension.
|
||||
if (!XDamageQueryExtension(display(), &damage_event_base_,
|
||||
&damage_error_base_)) {
|
||||
RTC_LOG(LS_INFO) << "X server does not support XDamage.";
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(lambroslambrou): Disable DAMAGE in situations where it is known
|
||||
// to fail, such as when Desktop Effects are enabled, with graphics
|
||||
// drivers (nVidia, ATI) that fail to report DAMAGE notifications
|
||||
// properly.
|
||||
|
||||
// Request notifications every time the screen becomes damaged.
|
||||
damage_handle_ =
|
||||
XDamageCreate(display(), root_window_, XDamageReportNonEmpty);
|
||||
if (!damage_handle_) {
|
||||
RTC_LOG(LS_ERROR) << "Unable to initialize XDamage.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an XFixes server-side region to collate damage into.
|
||||
damage_region_ = XFixesCreateRegion(display(), 0, 0);
|
||||
if (!damage_region_) {
|
||||
XDamageDestroy(display(), damage_handle_);
|
||||
RTC_LOG(LS_ERROR) << "Unable to create XFixes region.";
|
||||
return;
|
||||
}
|
||||
|
||||
options_.x_display()->AddEventHandler(damage_event_base_ + XDamageNotify,
|
||||
this);
|
||||
|
||||
use_damage_ = true;
|
||||
RTC_LOG(LS_INFO) << "Using XDamage extension.";
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
void ScreenCapturerX11::InitXrandr() {
|
||||
int major_version = 0;
|
||||
int minor_version = 0;
|
||||
int error_base_ignored = 0;
|
||||
if (XRRQueryExtension(display(), &randr_event_base_, &error_base_ignored) &&
|
||||
XRRQueryVersion(display(), &major_version, &minor_version)) {
|
||||
if (major_version > 1 || (major_version == 1 && minor_version >= 5)) {
|
||||
// Dynamically link XRRGetMonitors and XRRFreeMonitors as a workaround
|
||||
// to avoid a dependency issue with Debian 8.
|
||||
get_monitors_ = reinterpret_cast<get_monitors_func>(
|
||||
dlsym(RTLD_DEFAULT, "XRRGetMonitors"));
|
||||
free_monitors_ = reinterpret_cast<free_monitors_func>(
|
||||
dlsym(RTLD_DEFAULT, "XRRFreeMonitors"));
|
||||
if (get_monitors_ && free_monitors_) {
|
||||
use_randr_ = true;
|
||||
RTC_LOG(LS_INFO) << "Using XRandR extension v" << major_version << '.'
|
||||
<< minor_version << '.';
|
||||
monitors_ =
|
||||
get_monitors_(display(), root_window_, true, &num_monitors_);
|
||||
|
||||
// Register for screen change notifications
|
||||
XRRSelectInput(display(), root_window_, RRScreenChangeNotifyMask);
|
||||
options_.x_display()->AddEventHandler(
|
||||
randr_event_base_ + RRScreenChangeNotify, this);
|
||||
} else {
|
||||
RTC_LOG(LS_ERROR) << "Unable to link XRandR monitor functions.";
|
||||
}
|
||||
} else {
|
||||
RTC_LOG(LS_ERROR) << "XRandR entension is older than v1.5.";
|
||||
}
|
||||
} else {
|
||||
RTC_LOG(LS_ERROR) << "X server does not support XRandR.";
|
||||
}
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
void ScreenCapturerX11::UpdateMonitors() {
|
||||
// The queue should be reset whenever |selected_monitor_rect_| changes, so
|
||||
// that the DCHECKs in CaptureScreen() are satisfied.
|
||||
queue_.Reset();
|
||||
|
||||
if (monitors_) {
|
||||
free_monitors_(monitors_);
|
||||
monitors_ = nullptr;
|
||||
}
|
||||
|
||||
monitors_ = get_monitors_(display(), root_window_, true, &num_monitors_);
|
||||
|
||||
if (selected_monitor_name_) {
|
||||
if (selected_monitor_name_ == static_cast<Atom>(kFullDesktopScreenId)) {
|
||||
selected_monitor_rect_ =
|
||||
DesktopRect::MakeSize(x_server_pixel_buffer_.window_size());
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_monitors_; ++i) {
|
||||
XRRMonitorInfo& m = monitors_[i];
|
||||
if (selected_monitor_name_ == m.name) {
|
||||
RTC_LOG(LS_INFO) << "XRandR monitor " << m.name << " rect updated.";
|
||||
selected_monitor_rect_ =
|
||||
DesktopRect::MakeXYWH(m.x, m.y, m.width, m.height);
|
||||
const auto& pixel_buffer_rect = x_server_pixel_buffer_.window_rect();
|
||||
if (!pixel_buffer_rect.ContainsRect(selected_monitor_rect_)) {
|
||||
// This is never expected to happen, but crop the rectangle anyway
|
||||
// just in case the server returns inconsistent information.
|
||||
// CaptureScreen() expects `selected_monitor_rect_` to lie within
|
||||
// the pixel-buffer's rectangle.
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Cropping selected monitor rect to fit the pixel-buffer.";
|
||||
selected_monitor_rect_.IntersectWith(pixel_buffer_rect);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The selected monitor is not connected anymore
|
||||
RTC_LOG(LS_INFO) << "XRandR selected monitor " << selected_monitor_name_
|
||||
<< " lost.";
|
||||
selected_monitor_rect_ = DesktopRect::MakeWH(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerX11::Start(Callback* callback) {
|
||||
RTC_DCHECK(!callback_);
|
||||
RTC_DCHECK(callback);
|
||||
|
||||
callback_ = callback;
|
||||
}
|
||||
|
||||
void ScreenCapturerX11::CaptureFrame() {
|
||||
TRACE_EVENT0("webrtc", "ScreenCapturerX11::CaptureFrame");
|
||||
int64_t capture_start_time_nanos = rtc::TimeNanos();
|
||||
|
||||
queue_.MoveToNextFrame();
|
||||
if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
|
||||
RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared.";
|
||||
}
|
||||
|
||||
// Process XEvents for XDamage and cursor shape tracking.
|
||||
options_.x_display()->ProcessPendingXEvents();
|
||||
|
||||
// ProcessPendingXEvents() may call ScreenConfigurationChanged() which
|
||||
// reinitializes `x_server_pixel_buffer_`. Check if the pixel buffer is still
|
||||
// in a good shape.
|
||||
if (!x_server_pixel_buffer_.is_initialized()) {
|
||||
// We failed to initialize pixel buffer.
|
||||
RTC_LOG(LS_ERROR) << "Pixel buffer is not initialized.";
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate the current frame buffer only if it is not already allocated.
|
||||
// Note that we can't reallocate other buffers at this point, since the caller
|
||||
// may still be reading from them.
|
||||
if (!queue_.current_frame()) {
|
||||
std::unique_ptr<DesktopFrame> frame(
|
||||
new BasicDesktopFrame(selected_monitor_rect_.size()));
|
||||
|
||||
// We set the top-left of the frame so the mouse cursor will be composited
|
||||
// properly, and our frame buffer will not be overrun while blitting.
|
||||
frame->set_top_left(selected_monitor_rect_.top_left());
|
||||
queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame)));
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> result = CaptureScreen();
|
||||
if (!result) {
|
||||
RTC_LOG(LS_WARNING) << "Temporarily failed to capture screen.";
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
last_invalid_region_ = result->updated_region();
|
||||
result->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) /
|
||||
rtc::kNumNanosecsPerMillisec);
|
||||
result->set_capturer_id(DesktopCapturerId::kX11CapturerLinux);
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(result));
|
||||
}
|
||||
|
||||
bool ScreenCapturerX11::GetSourceList(SourceList* sources) {
|
||||
RTC_DCHECK(sources->size() == 0);
|
||||
if (!use_randr_) {
|
||||
sources->push_back({});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure that `monitors_` is updated with changes that may have happened
|
||||
// between calls to GetSourceList().
|
||||
options_.x_display()->ProcessPendingXEvents();
|
||||
|
||||
for (int i = 0; i < num_monitors_; ++i) {
|
||||
XRRMonitorInfo& m = monitors_[i];
|
||||
char* monitor_title = XGetAtomName(display(), m.name);
|
||||
|
||||
// Note name is an X11 Atom used to id the monitor.
|
||||
sources->push_back({static_cast<SourceId>(m.name), monitor_title});
|
||||
XFree(monitor_title);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScreenCapturerX11::SelectSource(SourceId id) {
|
||||
// Prevent the reuse of any frame buffers allocated for a previously selected
|
||||
// source. This is required to stop crashes, or old data from appearing in
|
||||
// a captured frame, when the new source is sized differently then the source
|
||||
// that was selected at the time a reused frame buffer was created.
|
||||
queue_.Reset();
|
||||
|
||||
if (!use_randr_ || id == kFullDesktopScreenId) {
|
||||
selected_monitor_name_ = kFullDesktopScreenId;
|
||||
selected_monitor_rect_ =
|
||||
DesktopRect::MakeSize(x_server_pixel_buffer_.window_size());
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_monitors_; ++i) {
|
||||
if (id == static_cast<SourceId>(monitors_[i].name)) {
|
||||
RTC_LOG(LS_INFO) << "XRandR selected source: " << id;
|
||||
XRRMonitorInfo& m = monitors_[i];
|
||||
selected_monitor_name_ = m.name;
|
||||
selected_monitor_rect_ =
|
||||
DesktopRect::MakeXYWH(m.x, m.y, m.width, m.height);
|
||||
const auto& pixel_buffer_rect = x_server_pixel_buffer_.window_rect();
|
||||
if (!pixel_buffer_rect.ContainsRect(selected_monitor_rect_)) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Cropping selected monitor rect to fit the pixel-buffer.";
|
||||
selected_monitor_rect_.IntersectWith(pixel_buffer_rect);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScreenCapturerX11::HandleXEvent(const XEvent& event) {
|
||||
if (use_damage_ && (event.type == damage_event_base_ + XDamageNotify)) {
|
||||
const XDamageNotifyEvent* damage_event =
|
||||
reinterpret_cast<const XDamageNotifyEvent*>(&event);
|
||||
if (damage_event->damage != damage_handle_)
|
||||
return false;
|
||||
RTC_DCHECK(damage_event->level == XDamageReportNonEmpty);
|
||||
return true;
|
||||
} else if (use_randr_ &&
|
||||
event.type == randr_event_base_ + RRScreenChangeNotify) {
|
||||
XRRUpdateConfiguration(const_cast<XEvent*>(&event));
|
||||
UpdateMonitors();
|
||||
RTC_LOG(LS_INFO) << "XRandR screen change event received.";
|
||||
return false;
|
||||
} else if (event.type == ConfigureNotify) {
|
||||
ScreenConfigurationChanged();
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> ScreenCapturerX11::CaptureScreen() {
|
||||
std::unique_ptr<SharedDesktopFrame> frame = queue_.current_frame()->Share();
|
||||
RTC_DCHECK(selected_monitor_rect_.size().equals(frame->size()));
|
||||
RTC_DCHECK(selected_monitor_rect_.top_left().equals(frame->top_left()));
|
||||
|
||||
// Pass the screen size to the helper, so it can clip the invalid region if it
|
||||
// expands that region to a grid. Note that the helper operates in the
|
||||
// DesktopFrame coordinate system where the top-left pixel is (0, 0), even for
|
||||
// a monitor with non-zero offset relative to `x_server_pixel_buffer_`.
|
||||
helper_.set_size_most_recent(frame->size());
|
||||
|
||||
// In the DAMAGE case, ensure the frame is up-to-date with the previous frame
|
||||
// if any. If there isn't a previous frame, that means a screen-resolution
|
||||
// change occurred, and `invalid_rects` will be updated to include the whole
|
||||
// screen.
|
||||
if (use_damage_ && queue_.previous_frame())
|
||||
SynchronizeFrame();
|
||||
|
||||
DesktopRegion* updated_region = frame->mutable_updated_region();
|
||||
|
||||
x_server_pixel_buffer_.Synchronize();
|
||||
if (use_damage_ && queue_.previous_frame()) {
|
||||
// Atomically fetch and clear the damage region.
|
||||
XDamageSubtract(display(), damage_handle_, None, damage_region_);
|
||||
int rects_num = 0;
|
||||
XRectangle bounds;
|
||||
XRectangle* rects = XFixesFetchRegionAndBounds(display(), damage_region_,
|
||||
&rects_num, &bounds);
|
||||
for (int i = 0; i < rects_num; ++i) {
|
||||
auto damage_rect = DesktopRect::MakeXYWH(rects[i].x, rects[i].y,
|
||||
rects[i].width, rects[i].height);
|
||||
|
||||
// Damage-regions are relative to `x_server_pixel_buffer`, so convert the
|
||||
// region to DesktopFrame coordinates where the top-left is always (0, 0),
|
||||
// before adding to the frame's updated_region. `helper_` also operates in
|
||||
// DesktopFrame coordinates, and it will take care of cropping away any
|
||||
// damage-regions that lie outside the selected monitor.
|
||||
damage_rect.Translate(-frame->top_left());
|
||||
updated_region->AddRect(damage_rect);
|
||||
}
|
||||
XFree(rects);
|
||||
helper_.InvalidateRegion(*updated_region);
|
||||
|
||||
// Capture the damaged portions of the desktop.
|
||||
helper_.TakeInvalidRegion(updated_region);
|
||||
|
||||
for (DesktopRegion::Iterator it(*updated_region); !it.IsAtEnd();
|
||||
it.Advance()) {
|
||||
auto rect = it.rect();
|
||||
rect.Translate(frame->top_left());
|
||||
if (!x_server_pixel_buffer_.CaptureRect(rect, frame.get()))
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// Doing full-screen polling, or this is the first capture after a
|
||||
// screen-resolution change. In either case, need a full-screen capture.
|
||||
if (!x_server_pixel_buffer_.CaptureRect(selected_monitor_rect_,
|
||||
frame.get())) {
|
||||
return nullptr;
|
||||
}
|
||||
updated_region->SetRect(DesktopRect::MakeSize(frame->size()));
|
||||
}
|
||||
|
||||
return std::move(frame);
|
||||
}
|
||||
|
||||
void ScreenCapturerX11::ScreenConfigurationChanged() {
|
||||
TRACE_EVENT0("webrtc", "ScreenCapturerX11::ScreenConfigurationChanged");
|
||||
// Make sure the frame buffers will be reallocated.
|
||||
queue_.Reset();
|
||||
|
||||
helper_.ClearInvalidRegion();
|
||||
if (!x_server_pixel_buffer_.Init(atom_cache_.get(),
|
||||
DefaultRootWindow(display()))) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer after screen "
|
||||
"configuration change.";
|
||||
}
|
||||
|
||||
if (use_randr_) {
|
||||
// Adding/removing RANDR monitors can generate a ConfigureNotify event
|
||||
// without generating any RRScreenChangeNotify event. So it is important to
|
||||
// update the monitors here even if the screen resolution hasn't changed.
|
||||
UpdateMonitors();
|
||||
} else {
|
||||
selected_monitor_rect_ =
|
||||
DesktopRect::MakeSize(x_server_pixel_buffer_.window_size());
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerX11::SynchronizeFrame() {
|
||||
// Synchronize the current buffer with the previous one since we do not
|
||||
// capture the entire desktop. Note that encoder may be reading from the
|
||||
// previous buffer at this time so thread access complaints are false
|
||||
// positives.
|
||||
|
||||
// TODO(hclam): We can reduce the amount of copying here by subtracting
|
||||
// `capturer_helper_`s region from `last_invalid_region_`.
|
||||
// http://crbug.com/92354
|
||||
RTC_DCHECK(queue_.previous_frame());
|
||||
|
||||
DesktopFrame* current = queue_.current_frame();
|
||||
DesktopFrame* last = queue_.previous_frame();
|
||||
RTC_DCHECK(current != last);
|
||||
for (DesktopRegion::Iterator it(last_invalid_region_); !it.IsAtEnd();
|
||||
it.Advance()) {
|
||||
const DesktopRect& r = it.rect();
|
||||
current->CopyPixelsFrom(*last, r.top_left(), r);
|
||||
}
|
||||
}
|
||||
|
||||
RTC_NO_SANITIZE("cfi-icall")
|
||||
void ScreenCapturerX11::DeinitXlib() {
|
||||
if (monitors_) {
|
||||
free_monitors_(monitors_);
|
||||
monitors_ = nullptr;
|
||||
}
|
||||
|
||||
if (gc_) {
|
||||
XFreeGC(display(), gc_);
|
||||
gc_ = nullptr;
|
||||
}
|
||||
|
||||
x_server_pixel_buffer_.Release();
|
||||
|
||||
if (display()) {
|
||||
if (damage_handle_) {
|
||||
XDamageDestroy(display(), damage_handle_);
|
||||
damage_handle_ = 0;
|
||||
}
|
||||
|
||||
if (damage_region_) {
|
||||
XFixesDestroyRegion(display(), damage_region_);
|
||||
damage_region_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> ScreenCapturerX11::CreateRawScreenCapturer(
|
||||
const DesktopCaptureOptions& options) {
|
||||
if (!options.x_display())
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<ScreenCapturerX11> capturer(new ScreenCapturerX11());
|
||||
if (!capturer.get()->Init(options)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::move(capturer);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_SCREEN_CAPTURER_X11_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_SCREEN_CAPTURER_X11_H_
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/Xdamage.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/linux/x11/shared_x_display.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_atom_cache.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h"
|
||||
#include "modules/desktop_capture/screen_capture_frame_queue.h"
|
||||
#include "modules/desktop_capture/screen_capturer_helper.h"
|
||||
#include "modules/desktop_capture/shared_desktop_frame.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A class to perform video frame capturing for Linux on X11.
|
||||
//
|
||||
// If XDamage is used, this class sets DesktopFrame::updated_region() according
|
||||
// to the areas reported by XDamage. Otherwise this class does not detect
|
||||
// DesktopFrame::updated_region(), the field is always set to the entire frame
|
||||
// rectangle. ScreenCapturerDifferWrapper should be used if that functionality
|
||||
// is necessary.
|
||||
class ScreenCapturerX11 : public DesktopCapturer,
|
||||
public SharedXDisplay::XEventHandler {
|
||||
public:
|
||||
ScreenCapturerX11();
|
||||
~ScreenCapturerX11() override;
|
||||
|
||||
ScreenCapturerX11(const ScreenCapturerX11&) = delete;
|
||||
ScreenCapturerX11& operator=(const ScreenCapturerX11&) = delete;
|
||||
|
||||
static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer(
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
// TODO(ajwong): Do we really want this to be synchronous?
|
||||
bool Init(const DesktopCaptureOptions& options);
|
||||
|
||||
// DesktopCapturer interface.
|
||||
void Start(Callback* delegate) override;
|
||||
void CaptureFrame() override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
|
||||
private:
|
||||
Display* display() { return options_.x_display()->display(); }
|
||||
|
||||
// SharedXDisplay::XEventHandler interface.
|
||||
bool HandleXEvent(const XEvent& event) override;
|
||||
|
||||
void InitXDamage();
|
||||
void InitXrandr();
|
||||
void UpdateMonitors();
|
||||
|
||||
// Capture screen pixels to the current buffer in the queue. In the DAMAGE
|
||||
// case, the ScreenCapturerHelper already holds the list of invalid rectangles
|
||||
// from HandleXEvent(). In the non-DAMAGE case, this captures the
|
||||
// whole screen, then calculates some invalid rectangles that include any
|
||||
// differences between this and the previous capture.
|
||||
std::unique_ptr<DesktopFrame> CaptureScreen();
|
||||
|
||||
// Called when the screen configuration is changed.
|
||||
void ScreenConfigurationChanged();
|
||||
|
||||
// Synchronize the current buffer with `last_buffer_`, by copying pixels from
|
||||
// the area of `last_invalid_rects`.
|
||||
// Note this only works on the assumption that kNumBuffers == 2, as
|
||||
// `last_invalid_rects` holds the differences from the previous buffer and
|
||||
// the one prior to that (which will then be the current buffer).
|
||||
void SynchronizeFrame();
|
||||
|
||||
void DeinitXlib();
|
||||
|
||||
DesktopCaptureOptions options_;
|
||||
|
||||
Callback* callback_ = nullptr;
|
||||
|
||||
// X11 graphics context.
|
||||
GC gc_ = nullptr;
|
||||
Window root_window_ = BadValue;
|
||||
|
||||
// XRandR 1.5 monitors.
|
||||
bool use_randr_ = false;
|
||||
int randr_event_base_ = 0;
|
||||
XRRMonitorInfo* monitors_ = nullptr;
|
||||
int num_monitors_ = 0;
|
||||
DesktopRect selected_monitor_rect_;
|
||||
// selected_monitor_name_ will be changed to kFullDesktopScreenId
|
||||
// by a call to SelectSource() at the end of Init() because
|
||||
// selected_monitor_rect_ should be updated as well.
|
||||
// Setting it to kFullDesktopScreenId here might be misleading.
|
||||
Atom selected_monitor_name_ = 0;
|
||||
typedef XRRMonitorInfo* (*get_monitors_func)(Display*, Window, Bool, int*);
|
||||
typedef void (*free_monitors_func)(XRRMonitorInfo*);
|
||||
get_monitors_func get_monitors_ = nullptr;
|
||||
free_monitors_func free_monitors_ = nullptr;
|
||||
|
||||
// XFixes.
|
||||
bool has_xfixes_ = false;
|
||||
int xfixes_event_base_ = -1;
|
||||
int xfixes_error_base_ = -1;
|
||||
|
||||
// XDamage information.
|
||||
bool use_damage_ = false;
|
||||
Damage damage_handle_ = 0;
|
||||
int damage_event_base_ = -1;
|
||||
int damage_error_base_ = -1;
|
||||
XserverRegion damage_region_ = 0;
|
||||
|
||||
// Access to the X Server's pixel buffer.
|
||||
XServerPixelBuffer x_server_pixel_buffer_;
|
||||
|
||||
// A thread-safe list of invalid rectangles, and the size of the most
|
||||
// recently captured screen.
|
||||
ScreenCapturerHelper helper_;
|
||||
|
||||
// Queue of the frames buffers.
|
||||
ScreenCaptureFrameQueue<SharedDesktopFrame> queue_;
|
||||
|
||||
// Invalid region from the previous capture. This is used to synchronize the
|
||||
// current with the last buffer used.
|
||||
DesktopRegion last_invalid_region_;
|
||||
|
||||
std::unique_ptr<XAtomCache> atom_cache_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_SCREEN_CAPTURER_X11_H_
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/shared_x_display.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
SharedXDisplay::SharedXDisplay(Display* display) : display_(display) {
|
||||
RTC_DCHECK(display_);
|
||||
}
|
||||
|
||||
SharedXDisplay::~SharedXDisplay() {
|
||||
RTC_DCHECK(event_handlers_.empty());
|
||||
XCloseDisplay(display_);
|
||||
}
|
||||
|
||||
// static
|
||||
rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::Create(
|
||||
absl::string_view display_name) {
|
||||
Display* display = XOpenDisplay(
|
||||
display_name.empty() ? NULL : std::string(display_name).c_str());
|
||||
if (!display) {
|
||||
RTC_LOG(LS_ERROR) << "Unable to open display";
|
||||
return nullptr;
|
||||
}
|
||||
return rtc::scoped_refptr<SharedXDisplay>(new SharedXDisplay(display));
|
||||
}
|
||||
|
||||
// static
|
||||
rtc::scoped_refptr<SharedXDisplay> SharedXDisplay::CreateDefault() {
|
||||
return Create(std::string());
|
||||
}
|
||||
|
||||
void SharedXDisplay::AddEventHandler(int type, XEventHandler* handler) {
|
||||
MutexLock lock(&mutex_);
|
||||
event_handlers_[type].push_back(handler);
|
||||
}
|
||||
|
||||
void SharedXDisplay::RemoveEventHandler(int type, XEventHandler* handler) {
|
||||
MutexLock lock(&mutex_);
|
||||
EventHandlersMap::iterator handlers = event_handlers_.find(type);
|
||||
if (handlers == event_handlers_.end())
|
||||
return;
|
||||
|
||||
std::vector<XEventHandler*>::iterator new_end =
|
||||
std::remove(handlers->second.begin(), handlers->second.end(), handler);
|
||||
handlers->second.erase(new_end, handlers->second.end());
|
||||
|
||||
// Check if no handlers left for this event.
|
||||
if (handlers->second.empty())
|
||||
event_handlers_.erase(handlers);
|
||||
}
|
||||
|
||||
void SharedXDisplay::ProcessPendingXEvents() {
|
||||
// Hold reference to `this` to prevent it from being destroyed while
|
||||
// processing events.
|
||||
rtc::scoped_refptr<SharedXDisplay> self(this);
|
||||
|
||||
// Protect access to `event_handlers_` after incrementing the refcount for
|
||||
// `this` to ensure the instance is still valid when the lock is acquired.
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Find the number of events that are outstanding "now." We don't just loop
|
||||
// on XPending because we want to guarantee this terminates.
|
||||
int events_to_process = XPending(display());
|
||||
XEvent e;
|
||||
|
||||
for (int i = 0; i < events_to_process; i++) {
|
||||
XNextEvent(display(), &e);
|
||||
EventHandlersMap::iterator handlers = event_handlers_.find(e.type);
|
||||
if (handlers == event_handlers_.end())
|
||||
continue;
|
||||
for (std::vector<XEventHandler*>::iterator it = handlers->second.begin();
|
||||
it != handlers->second.end(); ++it) {
|
||||
if ((*it)->HandleXEvent(e))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SharedXDisplay::IgnoreXServerGrabs() {
|
||||
int test_event_base = 0;
|
||||
int test_error_base = 0;
|
||||
int major = 0;
|
||||
int minor = 0;
|
||||
if (XTestQueryExtension(display(), &test_event_base, &test_error_base, &major,
|
||||
&minor)) {
|
||||
XTestGrabControl(display(), true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_SHARED_X_DISPLAY_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_SHARED_X_DISPLAY_H_
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "api/ref_counted_base.h"
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
#include "rtc_base/thread_annotations.h"
|
||||
|
||||
// Including Xlib.h will involve evil defines (Bool, Status, True, False), which
|
||||
// easily conflict with other headers.
|
||||
typedef struct _XDisplay Display;
|
||||
typedef union _XEvent XEvent;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A ref-counted object to store XDisplay connection.
|
||||
class RTC_EXPORT SharedXDisplay
|
||||
: public rtc::RefCountedNonVirtual<SharedXDisplay> {
|
||||
public:
|
||||
class XEventHandler {
|
||||
public:
|
||||
virtual ~XEventHandler() {}
|
||||
|
||||
// Processes XEvent. Returns true if the event has been handled.
|
||||
virtual bool HandleXEvent(const XEvent& event) = 0;
|
||||
};
|
||||
|
||||
// Creates a new X11 Display for the `display_name`. NULL is returned if X11
|
||||
// connection failed. Equivalent to CreateDefault() when `display_name` is
|
||||
// empty.
|
||||
static rtc::scoped_refptr<SharedXDisplay> Create(
|
||||
absl::string_view display_name);
|
||||
|
||||
// Creates X11 Display connection for the default display (e.g. specified in
|
||||
// DISPLAY). NULL is returned if X11 connection failed.
|
||||
static rtc::scoped_refptr<SharedXDisplay> CreateDefault();
|
||||
|
||||
Display* display() { return display_; }
|
||||
|
||||
// Adds a new event `handler` for XEvent's of `type`.
|
||||
void AddEventHandler(int type, XEventHandler* handler);
|
||||
|
||||
// Removes event `handler` added using `AddEventHandler`. Doesn't do anything
|
||||
// if `handler` is not registered.
|
||||
void RemoveEventHandler(int type, XEventHandler* handler);
|
||||
|
||||
// Processes pending XEvents, calling corresponding event handlers.
|
||||
void ProcessPendingXEvents();
|
||||
|
||||
void IgnoreXServerGrabs();
|
||||
|
||||
~SharedXDisplay();
|
||||
|
||||
SharedXDisplay(const SharedXDisplay&) = delete;
|
||||
SharedXDisplay& operator=(const SharedXDisplay&) = delete;
|
||||
|
||||
protected:
|
||||
// Takes ownership of `display`.
|
||||
explicit SharedXDisplay(Display* display);
|
||||
|
||||
private:
|
||||
typedef std::map<int, std::vector<XEventHandler*> > EventHandlersMap;
|
||||
|
||||
Display* display_;
|
||||
|
||||
Mutex mutex_;
|
||||
|
||||
EventHandlersMap event_handlers_ RTC_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_SHARED_X_DISPLAY_H_
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/window_capturer_x11.h"
|
||||
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/extensions/Xcomposite.h>
|
||||
#include <X11/extensions/composite.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "modules/desktop_capture/desktop_capture_types.h"
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/desktop_region.h"
|
||||
#include "modules/desktop_capture/linux/x11/shared_x_display.h"
|
||||
#include "modules/desktop_capture/linux/x11/window_finder_x11.h"
|
||||
#include "modules/desktop_capture/linux/x11/window_list_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/trace_event.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
WindowCapturerX11::WindowCapturerX11(const DesktopCaptureOptions& options)
|
||||
: x_display_(options.x_display()),
|
||||
atom_cache_(display()),
|
||||
window_finder_(&atom_cache_) {
|
||||
int event_base, error_base, major_version, minor_version;
|
||||
if (XCompositeQueryExtension(display(), &event_base, &error_base) &&
|
||||
XCompositeQueryVersion(display(), &major_version, &minor_version) &&
|
||||
// XCompositeNameWindowPixmap() requires version 0.2
|
||||
(major_version > 0 || minor_version >= 2)) {
|
||||
has_composite_extension_ = true;
|
||||
} else {
|
||||
RTC_LOG(LS_INFO) << "Xcomposite extension not available or too old.";
|
||||
}
|
||||
|
||||
x_display_->AddEventHandler(ConfigureNotify, this);
|
||||
}
|
||||
|
||||
WindowCapturerX11::~WindowCapturerX11() {
|
||||
x_display_->RemoveEventHandler(ConfigureNotify, this);
|
||||
}
|
||||
|
||||
bool WindowCapturerX11::GetSourceList(SourceList* sources) {
|
||||
return GetWindowList(&atom_cache_, [this, sources](::Window window) {
|
||||
Source w;
|
||||
w.id = window;
|
||||
if (this->GetWindowTitle(window, &w.title)) {
|
||||
sources->push_back(w);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool WindowCapturerX11::SelectSource(SourceId id) {
|
||||
if (!x_server_pixel_buffer_.Init(&atom_cache_, id))
|
||||
return false;
|
||||
|
||||
// Tell the X server to send us window resizing events.
|
||||
XSelectInput(display(), id, StructureNotifyMask);
|
||||
|
||||
selected_window_ = id;
|
||||
|
||||
// In addition to needing X11 server-side support for Xcomposite, it actually
|
||||
// needs to be turned on for the window. If the user has modern
|
||||
// hardware/drivers but isn't using a compositing window manager, that won't
|
||||
// be the case. Here we automatically turn it on.
|
||||
|
||||
// Redirect drawing to an offscreen buffer (ie, turn on compositing). X11
|
||||
// remembers who has requested this and will turn it off for us when we exit.
|
||||
XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowCapturerX11::FocusOnSelectedSource() {
|
||||
if (!selected_window_)
|
||||
return false;
|
||||
|
||||
unsigned int num_children;
|
||||
::Window* children;
|
||||
::Window parent;
|
||||
::Window root;
|
||||
// Find the root window to pass event to.
|
||||
int status = XQueryTree(display(), selected_window_, &root, &parent,
|
||||
&children, &num_children);
|
||||
if (status == 0) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to query for the root window.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (children)
|
||||
XFree(children);
|
||||
|
||||
XRaiseWindow(display(), selected_window_);
|
||||
|
||||
// Some window managers (e.g., metacity in GNOME) consider it illegal to
|
||||
// raise a window without also giving it input focus with
|
||||
// _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough.
|
||||
Atom atom = XInternAtom(display(), "_NET_ACTIVE_WINDOW", True);
|
||||
if (atom != None) {
|
||||
XEvent xev;
|
||||
xev.xclient.type = ClientMessage;
|
||||
xev.xclient.serial = 0;
|
||||
xev.xclient.send_event = True;
|
||||
xev.xclient.window = selected_window_;
|
||||
xev.xclient.message_type = atom;
|
||||
|
||||
// The format member is set to 8, 16, or 32 and specifies whether the
|
||||
// data should be viewed as a list of bytes, shorts, or longs.
|
||||
xev.xclient.format = 32;
|
||||
|
||||
memset(xev.xclient.data.l, 0, sizeof(xev.xclient.data.l));
|
||||
|
||||
XSendEvent(display(), root, False,
|
||||
SubstructureRedirectMask | SubstructureNotifyMask, &xev);
|
||||
}
|
||||
XFlush(display());
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowCapturerX11::Start(Callback* callback) {
|
||||
RTC_DCHECK(!callback_);
|
||||
RTC_DCHECK(callback);
|
||||
|
||||
callback_ = callback;
|
||||
}
|
||||
|
||||
void WindowCapturerX11::CaptureFrame() {
|
||||
TRACE_EVENT0("webrtc", "WindowCapturerX11::CaptureFrame");
|
||||
|
||||
if (!x_server_pixel_buffer_.IsWindowValid()) {
|
||||
RTC_LOG(LS_ERROR) << "The window is no longer valid.";
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
x_display_->ProcessPendingXEvents();
|
||||
|
||||
if (!has_composite_extension_) {
|
||||
// Without the Xcomposite extension we capture when the whole window is
|
||||
// visible on screen and not covered by any other window. This is not
|
||||
// something we want so instead, just bail out.
|
||||
RTC_LOG(LS_ERROR) << "No Xcomposite extension detected.";
|
||||
callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetWindowState(&atom_cache_, selected_window_) == IconicState) {
|
||||
// Window is in minimized. Return a 1x1 frame as same as OSX/Win does.
|
||||
std::unique_ptr<DesktopFrame> frame(
|
||||
new BasicDesktopFrame(DesktopSize(1, 1)));
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> frame(
|
||||
new BasicDesktopFrame(x_server_pixel_buffer_.window_size()));
|
||||
|
||||
x_server_pixel_buffer_.Synchronize();
|
||||
if (!x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()),
|
||||
frame.get())) {
|
||||
RTC_LOG(LS_WARNING) << "Temporarily failed to capture winodw.";
|
||||
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
frame->mutable_updated_region()->SetRect(
|
||||
DesktopRect::MakeSize(frame->size()));
|
||||
frame->set_top_left(x_server_pixel_buffer_.window_rect().top_left());
|
||||
frame->set_capturer_id(DesktopCapturerId::kX11CapturerLinux);
|
||||
|
||||
callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
|
||||
}
|
||||
|
||||
bool WindowCapturerX11::IsOccluded(const DesktopVector& pos) {
|
||||
return window_finder_.GetWindowUnderPoint(pos) !=
|
||||
static_cast<WindowId>(selected_window_);
|
||||
}
|
||||
|
||||
bool WindowCapturerX11::HandleXEvent(const XEvent& event) {
|
||||
if (event.type == ConfigureNotify) {
|
||||
XConfigureEvent xce = event.xconfigure;
|
||||
if (xce.window == selected_window_) {
|
||||
if (!DesktopRectFromXAttributes(xce).equals(
|
||||
x_server_pixel_buffer_.window_rect())) {
|
||||
if (!x_server_pixel_buffer_.Init(&atom_cache_, selected_window_)) {
|
||||
RTC_LOG(LS_ERROR)
|
||||
<< "Failed to initialize pixel buffer after resizing.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always returns false, so other observers can still receive the events.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WindowCapturerX11::GetWindowTitle(::Window window, std::string* title) {
|
||||
int status;
|
||||
bool result = false;
|
||||
XTextProperty window_name;
|
||||
window_name.value = nullptr;
|
||||
if (window) {
|
||||
status = XGetWMName(display(), window, &window_name);
|
||||
if (status && window_name.value && window_name.nitems) {
|
||||
int cnt;
|
||||
char** list = nullptr;
|
||||
status =
|
||||
Xutf8TextPropertyToTextList(display(), &window_name, &list, &cnt);
|
||||
if (status >= Success && cnt && *list) {
|
||||
if (cnt > 1) {
|
||||
RTC_LOG(LS_INFO) << "Window has " << cnt
|
||||
<< " text properties, only using the first one.";
|
||||
}
|
||||
*title = *list;
|
||||
result = true;
|
||||
}
|
||||
if (list)
|
||||
XFreeStringList(list);
|
||||
}
|
||||
if (window_name.value)
|
||||
XFree(window_name.value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopCapturer> WindowCapturerX11::CreateRawWindowCapturer(
|
||||
const DesktopCaptureOptions& options) {
|
||||
if (!options.x_display())
|
||||
return nullptr;
|
||||
return std::unique_ptr<DesktopCapturer>(new WindowCapturerX11(options));
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_CAPTURER_X11_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_CAPTURER_X11_H_
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "modules/desktop_capture/desktop_capture_options.h"
|
||||
#include "modules/desktop_capture/desktop_capturer.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/linux/x11/shared_x_display.h"
|
||||
#include "modules/desktop_capture/linux/x11/window_finder_x11.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_atom_cache.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class WindowCapturerX11 : public DesktopCapturer,
|
||||
public SharedXDisplay::XEventHandler {
|
||||
public:
|
||||
explicit WindowCapturerX11(const DesktopCaptureOptions& options);
|
||||
~WindowCapturerX11() override;
|
||||
|
||||
WindowCapturerX11(const WindowCapturerX11&) = delete;
|
||||
WindowCapturerX11& operator=(const WindowCapturerX11&) = delete;
|
||||
|
||||
static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer(
|
||||
const DesktopCaptureOptions& options);
|
||||
|
||||
// DesktopCapturer interface.
|
||||
void Start(Callback* callback) override;
|
||||
void CaptureFrame() override;
|
||||
bool GetSourceList(SourceList* sources) override;
|
||||
bool SelectSource(SourceId id) override;
|
||||
bool FocusOnSelectedSource() override;
|
||||
bool IsOccluded(const DesktopVector& pos) override;
|
||||
|
||||
// SharedXDisplay::XEventHandler interface.
|
||||
bool HandleXEvent(const XEvent& event) override;
|
||||
|
||||
private:
|
||||
Display* display() { return x_display_->display(); }
|
||||
|
||||
// Returns window title for the specified X `window`.
|
||||
bool GetWindowTitle(::Window window, std::string* title);
|
||||
|
||||
Callback* callback_ = nullptr;
|
||||
|
||||
rtc::scoped_refptr<SharedXDisplay> x_display_;
|
||||
|
||||
bool has_composite_extension_ = false;
|
||||
|
||||
::Window selected_window_ = 0;
|
||||
XServerPixelBuffer x_server_pixel_buffer_;
|
||||
XAtomCache atom_cache_;
|
||||
WindowFinderX11 window_finder_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_CAPTURER_X11_H_
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/window_finder_x11.h"
|
||||
|
||||
#include <X11/X.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/window_list_utils.h"
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
WindowFinderX11::WindowFinderX11(XAtomCache* cache) : cache_(cache) {
|
||||
RTC_DCHECK(cache_);
|
||||
}
|
||||
|
||||
WindowFinderX11::~WindowFinderX11() = default;
|
||||
|
||||
WindowId WindowFinderX11::GetWindowUnderPoint(DesktopVector point) {
|
||||
WindowId id = kNullWindowId;
|
||||
GetWindowList(cache_, [&id, this, point](::Window window) {
|
||||
DesktopRect rect;
|
||||
if (GetWindowRect(this->cache_->display(), window, &rect) &&
|
||||
rect.Contains(point)) {
|
||||
id = window;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return id;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<WindowFinder> WindowFinder::Create(
|
||||
const WindowFinder::Options& options) {
|
||||
if (options.cache == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<WindowFinderX11>(options.cache);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_FINDER_X11_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_FINDER_X11_H_
|
||||
|
||||
#include "modules/desktop_capture/window_finder.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class XAtomCache;
|
||||
|
||||
// The implementation of WindowFinder for X11.
|
||||
class WindowFinderX11 final : public WindowFinder {
|
||||
public:
|
||||
explicit WindowFinderX11(XAtomCache* cache);
|
||||
~WindowFinderX11() override;
|
||||
|
||||
// WindowFinder implementation.
|
||||
WindowId GetWindowUnderPoint(DesktopVector point) override;
|
||||
|
||||
private:
|
||||
XAtomCache* const cache_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_FINDER_X11_H_
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/window_list_utils.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/x_error_trap.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_window_property.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
class DeferXFree {
|
||||
public:
|
||||
explicit DeferXFree(void* data) : data_(data) {}
|
||||
~DeferXFree();
|
||||
|
||||
private:
|
||||
void* const data_;
|
||||
};
|
||||
|
||||
DeferXFree::~DeferXFree() {
|
||||
if (data_)
|
||||
XFree(data_);
|
||||
}
|
||||
|
||||
// Iterates through `window` hierarchy to find first visible window, i.e. one
|
||||
// that has WM_STATE property set to NormalState.
|
||||
// See http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1 .
|
||||
::Window GetApplicationWindow(XAtomCache* cache, ::Window window) {
|
||||
int32_t state = GetWindowState(cache, window);
|
||||
if (state == NormalState) {
|
||||
// Window has WM_STATE==NormalState. Return it.
|
||||
return window;
|
||||
} else if (state == IconicState) {
|
||||
// Window is in minimized. Skip it.
|
||||
return 0;
|
||||
}
|
||||
|
||||
RTC_DCHECK_EQ(state, WithdrawnState);
|
||||
// If the window is in WithdrawnState then look at all of its children.
|
||||
::Window root, parent;
|
||||
::Window* children;
|
||||
unsigned int num_children;
|
||||
if (!XQueryTree(cache->display(), window, &root, &parent, &children,
|
||||
&num_children)) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to query for child windows although window"
|
||||
"does not have a valid WM_STATE.";
|
||||
return 0;
|
||||
}
|
||||
::Window app_window = 0;
|
||||
for (unsigned int i = 0; i < num_children; ++i) {
|
||||
app_window = GetApplicationWindow(cache, children[i]);
|
||||
if (app_window)
|
||||
break;
|
||||
}
|
||||
|
||||
if (children)
|
||||
XFree(children);
|
||||
return app_window;
|
||||
}
|
||||
|
||||
// Returns true if the `window` is a desktop element.
|
||||
bool IsDesktopElement(XAtomCache* cache, ::Window window) {
|
||||
RTC_DCHECK(cache);
|
||||
if (window == 0)
|
||||
return false;
|
||||
|
||||
// First look for _NET_WM_WINDOW_TYPE. The standard
|
||||
// (http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2760306)
|
||||
// says this hint *should* be present on all windows, and we use the existence
|
||||
// of _NET_WM_WINDOW_TYPE_NORMAL in the property to indicate a window is not
|
||||
// a desktop element (that is, only "normal" windows should be shareable).
|
||||
XWindowProperty<uint32_t> window_type(cache->display(), window,
|
||||
cache->WindowType());
|
||||
if (window_type.is_valid() && window_type.size() > 0) {
|
||||
uint32_t* end = window_type.data() + window_type.size();
|
||||
bool is_normal =
|
||||
(end != std::find(window_type.data(), end, cache->WindowTypeNormal()));
|
||||
return !is_normal;
|
||||
}
|
||||
|
||||
// Fall back on using the hint.
|
||||
XClassHint class_hint;
|
||||
Status status = XGetClassHint(cache->display(), window, &class_hint);
|
||||
if (status == 0) {
|
||||
// No hints, assume this is a normal application window.
|
||||
return false;
|
||||
}
|
||||
|
||||
DeferXFree free_res_name(class_hint.res_name);
|
||||
DeferXFree free_res_class(class_hint.res_class);
|
||||
return strcmp("gnome-panel", class_hint.res_name) == 0 ||
|
||||
strcmp("desktop_window", class_hint.res_name) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int32_t GetWindowState(XAtomCache* cache, ::Window window) {
|
||||
// Get WM_STATE property of the window.
|
||||
XWindowProperty<uint32_t> window_state(cache->display(), window,
|
||||
cache->WmState());
|
||||
|
||||
// WM_STATE is considered to be set to WithdrawnState when it missing.
|
||||
return window_state.is_valid() ? *window_state.data() : WithdrawnState;
|
||||
}
|
||||
|
||||
bool GetWindowList(XAtomCache* cache,
|
||||
rtc::FunctionView<bool(::Window)> on_window) {
|
||||
RTC_DCHECK(cache);
|
||||
RTC_DCHECK(on_window);
|
||||
::Display* const display = cache->display();
|
||||
|
||||
int failed_screens = 0;
|
||||
const int num_screens = XScreenCount(display);
|
||||
for (int screen = 0; screen < num_screens; screen++) {
|
||||
::Window root_window = XRootWindow(display, screen);
|
||||
::Window parent;
|
||||
::Window* children;
|
||||
unsigned int num_children;
|
||||
{
|
||||
XErrorTrap error_trap(display);
|
||||
if (XQueryTree(display, root_window, &root_window, &parent, &children,
|
||||
&num_children) == 0 ||
|
||||
error_trap.GetLastErrorAndDisable() != 0) {
|
||||
failed_screens++;
|
||||
RTC_LOG(LS_ERROR) << "Failed to query for child windows for screen "
|
||||
<< screen;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DeferXFree free_children(children);
|
||||
|
||||
for (unsigned int i = 0; i < num_children; i++) {
|
||||
// Iterates in reverse order to return windows from front to back.
|
||||
::Window app_window =
|
||||
GetApplicationWindow(cache, children[num_children - 1 - i]);
|
||||
if (app_window && !IsDesktopElement(cache, app_window)) {
|
||||
if (!on_window(app_window)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return failed_screens < num_screens;
|
||||
}
|
||||
|
||||
bool GetWindowRect(::Display* display,
|
||||
::Window window,
|
||||
DesktopRect* rect,
|
||||
XWindowAttributes* attributes /* = nullptr */) {
|
||||
XWindowAttributes local_attributes;
|
||||
int offset_x;
|
||||
int offset_y;
|
||||
if (attributes == nullptr) {
|
||||
attributes = &local_attributes;
|
||||
}
|
||||
|
||||
{
|
||||
XErrorTrap error_trap(display);
|
||||
if (!XGetWindowAttributes(display, window, attributes) ||
|
||||
error_trap.GetLastErrorAndDisable() != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*rect = DesktopRectFromXAttributes(*attributes);
|
||||
|
||||
{
|
||||
XErrorTrap error_trap(display);
|
||||
::Window child;
|
||||
if (!XTranslateCoordinates(display, window, attributes->root, -rect->left(),
|
||||
-rect->top(), &offset_x, &offset_y, &child) ||
|
||||
error_trap.GetLastErrorAndDisable() != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
rect->Translate(offset_x, offset_y);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_LIST_UTILS_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_LIST_UTILS_H_
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api/function_view.h"
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_atom_cache.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Synchronously iterates all on-screen windows in `cache`.display() in
|
||||
// decreasing z-order and sends them one-by-one to `on_window` function before
|
||||
// GetWindowList() returns. If `on_window` returns false, this function ignores
|
||||
// other windows and returns immediately. GetWindowList() returns false if
|
||||
// native APIs failed. If multiple screens are attached to the `display`, this
|
||||
// function returns false only when native APIs failed on all screens. Menus,
|
||||
// panels and minimized windows will be ignored.
|
||||
bool GetWindowList(XAtomCache* cache,
|
||||
rtc::FunctionView<bool(::Window)> on_window);
|
||||
|
||||
// Returns WM_STATE property of the `window`. This function returns
|
||||
// WithdrawnState if the `window` is missing.
|
||||
int32_t GetWindowState(XAtomCache* cache, ::Window window);
|
||||
|
||||
// Returns the rectangle of the `window` in the coordinates of `display`. This
|
||||
// function returns false if native APIs failed. If `attributes` is provided, it
|
||||
// will be filled with the attributes of `window`. The `rect` is in system
|
||||
// coordinate, i.e. the primary monitor always starts from (0, 0).
|
||||
bool GetWindowRect(::Display* display,
|
||||
::Window window,
|
||||
DesktopRect* rect,
|
||||
XWindowAttributes* attributes = nullptr);
|
||||
|
||||
// Creates a DesktopRect from `attributes`.
|
||||
template <typename T>
|
||||
DesktopRect DesktopRectFromXAttributes(const T& attributes) {
|
||||
return DesktopRect::MakeXYWH(attributes.x, attributes.y, attributes.width,
|
||||
attributes.height);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_WINDOW_LIST_UTILS_H_
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/x_atom_cache.h"
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
XAtomCache::XAtomCache(::Display* display) : display_(display) {
|
||||
RTC_DCHECK(display_);
|
||||
}
|
||||
|
||||
XAtomCache::~XAtomCache() = default;
|
||||
|
||||
::Display* XAtomCache::display() const {
|
||||
return display_;
|
||||
}
|
||||
|
||||
Atom XAtomCache::WmState() {
|
||||
return CreateIfNotExist(&wm_state_, "WM_STATE");
|
||||
}
|
||||
|
||||
Atom XAtomCache::WindowType() {
|
||||
return CreateIfNotExist(&window_type_, "_NET_WM_WINDOW_TYPE");
|
||||
}
|
||||
|
||||
Atom XAtomCache::WindowTypeNormal() {
|
||||
return CreateIfNotExist(&window_type_normal_, "_NET_WM_WINDOW_TYPE_NORMAL");
|
||||
}
|
||||
|
||||
Atom XAtomCache::IccProfile() {
|
||||
return CreateIfNotExist(&icc_profile_, "_ICC_PROFILE");
|
||||
}
|
||||
|
||||
Atom XAtomCache::CreateIfNotExist(Atom* atom, const char* name) {
|
||||
RTC_DCHECK(atom);
|
||||
if (*atom == None) {
|
||||
*atom = XInternAtom(display(), name, True);
|
||||
}
|
||||
return *atom;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ATOM_CACHE_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ATOM_CACHE_H_
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// A cache of Atom. Each Atom object is created on demand.
|
||||
class XAtomCache final {
|
||||
public:
|
||||
explicit XAtomCache(::Display* display);
|
||||
~XAtomCache();
|
||||
|
||||
::Display* display() const;
|
||||
|
||||
Atom WmState();
|
||||
Atom WindowType();
|
||||
Atom WindowTypeNormal();
|
||||
Atom IccProfile();
|
||||
|
||||
private:
|
||||
// If |*atom| is None, this function uses XInternAtom() to retrieve an Atom.
|
||||
Atom CreateIfNotExist(Atom* atom, const char* name);
|
||||
|
||||
::Display* const display_;
|
||||
Atom wm_state_ = None;
|
||||
Atom window_type_ = None;
|
||||
Atom window_type_normal_ = None;
|
||||
Atom icc_profile_ = None;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ATOM_CACHE_H_
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/x_error_trap.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
static int g_last_xserver_error_code = 0;
|
||||
static std::atomic<Display*> g_display_for_error_handler = nullptr;
|
||||
|
||||
Mutex* AcquireMutex() {
|
||||
static Mutex* mutex = new Mutex();
|
||||
return mutex;
|
||||
}
|
||||
|
||||
int XServerErrorHandler(Display* display, XErrorEvent* error_event) {
|
||||
RTC_DCHECK_EQ(display, g_display_for_error_handler.load());
|
||||
g_last_xserver_error_code = error_event->error_code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
XErrorTrap::XErrorTrap(Display* display) : mutex_lock_(AcquireMutex()) {
|
||||
// We don't expect this class to be used in a nested fashion so therefore
|
||||
// g_display_for_error_handler should never be valid here.
|
||||
RTC_DCHECK(!g_display_for_error_handler.load());
|
||||
RTC_DCHECK(display);
|
||||
g_display_for_error_handler.store(display);
|
||||
g_last_xserver_error_code = 0;
|
||||
original_error_handler_ = XSetErrorHandler(&XServerErrorHandler);
|
||||
}
|
||||
|
||||
int XErrorTrap::GetLastErrorAndDisable() {
|
||||
g_display_for_error_handler.store(nullptr);
|
||||
XSetErrorHandler(original_error_handler_);
|
||||
return g_last_xserver_error_code;
|
||||
}
|
||||
|
||||
XErrorTrap::~XErrorTrap() {
|
||||
if (g_display_for_error_handler.load() != nullptr)
|
||||
GetLastErrorAndDisable();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Helper class that registers an X Window error handler. Caller can use
|
||||
// GetLastErrorAndDisable() to get the last error that was caught, if any.
|
||||
class XErrorTrap {
|
||||
public:
|
||||
explicit XErrorTrap(Display* display);
|
||||
|
||||
XErrorTrap(const XErrorTrap&) = delete;
|
||||
XErrorTrap& operator=(const XErrorTrap&) = delete;
|
||||
|
||||
~XErrorTrap();
|
||||
|
||||
// Returns the last error if one was caught, otherwise 0. Also unregisters the
|
||||
// error handler and replaces it with `original_error_handler_`.
|
||||
int GetLastErrorAndDisable();
|
||||
|
||||
private:
|
||||
MutexLock mutex_lock_;
|
||||
XErrorHandler original_error_handler_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_ERROR_TRAP_H_
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/x_server_pixel_buffer.h"
|
||||
|
||||
#include <X11/Xutil.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "modules/desktop_capture/linux/x11/window_list_utils.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_error_trap.h"
|
||||
#include "modules/desktop_capture/linux/x11/x_window_property.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns the number of bits `mask` has to be shifted left so its last
|
||||
// (most-significant) bit set becomes the most-significant bit of the word.
|
||||
// When `mask` is 0 the function returns 31.
|
||||
uint32_t MaskToShift(uint32_t mask) {
|
||||
int shift = 0;
|
||||
if ((mask & 0xffff0000u) == 0) {
|
||||
mask <<= 16;
|
||||
shift += 16;
|
||||
}
|
||||
if ((mask & 0xff000000u) == 0) {
|
||||
mask <<= 8;
|
||||
shift += 8;
|
||||
}
|
||||
if ((mask & 0xf0000000u) == 0) {
|
||||
mask <<= 4;
|
||||
shift += 4;
|
||||
}
|
||||
if ((mask & 0xc0000000u) == 0) {
|
||||
mask <<= 2;
|
||||
shift += 2;
|
||||
}
|
||||
if ((mask & 0x80000000u) == 0)
|
||||
shift += 1;
|
||||
|
||||
return shift;
|
||||
}
|
||||
|
||||
// Returns true if `image` is in RGB format.
|
||||
bool IsXImageRGBFormat(XImage* image) {
|
||||
return image->bits_per_pixel == 32 && image->red_mask == 0xff0000 &&
|
||||
image->green_mask == 0xff00 && image->blue_mask == 0xff;
|
||||
}
|
||||
|
||||
// We expose two forms of blitting to handle variations in the pixel format.
|
||||
// In FastBlit(), the operation is effectively a memcpy.
|
||||
void FastBlit(XImage* x_image,
|
||||
uint8_t* src_pos,
|
||||
const DesktopRect& rect,
|
||||
DesktopFrame* frame) {
|
||||
RTC_DCHECK_LE(frame->top_left().x(), rect.left());
|
||||
RTC_DCHECK_LE(frame->top_left().y(), rect.top());
|
||||
|
||||
frame->CopyPixelsFrom(
|
||||
src_pos, x_image->bytes_per_line,
|
||||
DesktopRect::MakeXYWH(rect.left() - frame->top_left().x(),
|
||||
rect.top() - frame->top_left().y(), rect.width(),
|
||||
rect.height()));
|
||||
}
|
||||
|
||||
void SlowBlit(XImage* x_image,
|
||||
uint8_t* src_pos,
|
||||
const DesktopRect& rect,
|
||||
DesktopFrame* frame) {
|
||||
RTC_DCHECK_LE(frame->top_left().x(), rect.left());
|
||||
RTC_DCHECK_LE(frame->top_left().y(), rect.top());
|
||||
|
||||
int src_stride = x_image->bytes_per_line;
|
||||
int dst_x = rect.left() - frame->top_left().x();
|
||||
int dst_y = rect.top() - frame->top_left().y();
|
||||
int width = rect.width(), height = rect.height();
|
||||
|
||||
uint32_t red_mask = x_image->red_mask;
|
||||
uint32_t green_mask = x_image->red_mask;
|
||||
uint32_t blue_mask = x_image->blue_mask;
|
||||
|
||||
uint32_t red_shift = MaskToShift(red_mask);
|
||||
uint32_t green_shift = MaskToShift(green_mask);
|
||||
uint32_t blue_shift = MaskToShift(blue_mask);
|
||||
|
||||
int bits_per_pixel = x_image->bits_per_pixel;
|
||||
|
||||
uint8_t* dst_pos = frame->data() + frame->stride() * dst_y;
|
||||
dst_pos += dst_x * DesktopFrame::kBytesPerPixel;
|
||||
// TODO(hclam): Optimize, perhaps using MMX code or by converting to
|
||||
// YUV directly.
|
||||
// TODO(sergeyu): This code doesn't handle XImage byte order properly and
|
||||
// won't work with 24bpp images. Fix it.
|
||||
for (int y = 0; y < height; y++) {
|
||||
uint32_t* dst_pos_32 = reinterpret_cast<uint32_t*>(dst_pos);
|
||||
uint32_t* src_pos_32 = reinterpret_cast<uint32_t*>(src_pos);
|
||||
uint16_t* src_pos_16 = reinterpret_cast<uint16_t*>(src_pos);
|
||||
for (int x = 0; x < width; x++) {
|
||||
// Dereference through an appropriately-aligned pointer.
|
||||
uint32_t pixel;
|
||||
if (bits_per_pixel == 32) {
|
||||
pixel = src_pos_32[x];
|
||||
} else if (bits_per_pixel == 16) {
|
||||
pixel = src_pos_16[x];
|
||||
} else {
|
||||
pixel = src_pos[x];
|
||||
}
|
||||
uint32_t r = (pixel & red_mask) << red_shift;
|
||||
uint32_t g = (pixel & green_mask) << green_shift;
|
||||
uint32_t b = (pixel & blue_mask) << blue_shift;
|
||||
// Write as 32-bit RGB.
|
||||
dst_pos_32[x] =
|
||||
((r >> 8) & 0xff0000) | ((g >> 16) & 0xff00) | ((b >> 24) & 0xff);
|
||||
}
|
||||
dst_pos += frame->stride();
|
||||
src_pos += src_stride;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
XServerPixelBuffer::XServerPixelBuffer() {}
|
||||
|
||||
XServerPixelBuffer::~XServerPixelBuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
void XServerPixelBuffer::Release() {
|
||||
if (x_image_) {
|
||||
XDestroyImage(x_image_);
|
||||
x_image_ = nullptr;
|
||||
}
|
||||
if (x_shm_image_) {
|
||||
XDestroyImage(x_shm_image_);
|
||||
x_shm_image_ = nullptr;
|
||||
}
|
||||
if (shm_pixmap_) {
|
||||
XFreePixmap(display_, shm_pixmap_);
|
||||
shm_pixmap_ = 0;
|
||||
}
|
||||
if (shm_gc_) {
|
||||
XFreeGC(display_, shm_gc_);
|
||||
shm_gc_ = nullptr;
|
||||
}
|
||||
|
||||
ReleaseSharedMemorySegment();
|
||||
|
||||
window_ = 0;
|
||||
}
|
||||
|
||||
void XServerPixelBuffer::ReleaseSharedMemorySegment() {
|
||||
if (!shm_segment_info_)
|
||||
return;
|
||||
if (shm_segment_info_->shmaddr != nullptr)
|
||||
shmdt(shm_segment_info_->shmaddr);
|
||||
if (shm_segment_info_->shmid != -1)
|
||||
shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
|
||||
delete shm_segment_info_;
|
||||
shm_segment_info_ = nullptr;
|
||||
}
|
||||
|
||||
bool XServerPixelBuffer::Init(XAtomCache* cache, Window window) {
|
||||
Release();
|
||||
display_ = cache->display();
|
||||
|
||||
XWindowAttributes attributes;
|
||||
if (!GetWindowRect(display_, window, &window_rect_, &attributes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cache->IccProfile() != None) {
|
||||
// `window` is the root window when doing screen capture.
|
||||
XWindowProperty<uint8_t> icc_profile_property(cache->display(), window,
|
||||
cache->IccProfile());
|
||||
if (icc_profile_property.is_valid() && icc_profile_property.size() > 0) {
|
||||
icc_profile_ = std::vector<uint8_t>(
|
||||
icc_profile_property.data(),
|
||||
icc_profile_property.data() + icc_profile_property.size());
|
||||
} else {
|
||||
RTC_LOG(LS_WARNING) << "Failed to get icc profile";
|
||||
}
|
||||
}
|
||||
|
||||
window_ = window;
|
||||
InitShm(attributes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XServerPixelBuffer::InitShm(const XWindowAttributes& attributes) {
|
||||
Visual* default_visual = attributes.visual;
|
||||
int default_depth = attributes.depth;
|
||||
|
||||
int major, minor;
|
||||
Bool have_pixmaps;
|
||||
if (!XShmQueryVersion(display_, &major, &minor, &have_pixmaps)) {
|
||||
// Shared memory not supported. CaptureRect will use the XImage API instead.
|
||||
return;
|
||||
}
|
||||
|
||||
bool using_shm = false;
|
||||
shm_segment_info_ = new XShmSegmentInfo;
|
||||
shm_segment_info_->shmid = -1;
|
||||
shm_segment_info_->shmaddr = nullptr;
|
||||
shm_segment_info_->readOnly = False;
|
||||
x_shm_image_ = XShmCreateImage(display_, default_visual, default_depth,
|
||||
ZPixmap, 0, shm_segment_info_,
|
||||
window_rect_.width(), window_rect_.height());
|
||||
if (x_shm_image_) {
|
||||
shm_segment_info_->shmid =
|
||||
shmget(IPC_PRIVATE, x_shm_image_->bytes_per_line * x_shm_image_->height,
|
||||
IPC_CREAT | 0600);
|
||||
if (shm_segment_info_->shmid != -1) {
|
||||
void* shmat_result = shmat(shm_segment_info_->shmid, 0, 0);
|
||||
if (shmat_result != reinterpret_cast<void*>(-1)) {
|
||||
shm_segment_info_->shmaddr = reinterpret_cast<char*>(shmat_result);
|
||||
x_shm_image_->data = shm_segment_info_->shmaddr;
|
||||
|
||||
XErrorTrap error_trap(display_);
|
||||
using_shm = XShmAttach(display_, shm_segment_info_);
|
||||
XSync(display_, False);
|
||||
if (error_trap.GetLastErrorAndDisable() != 0)
|
||||
using_shm = false;
|
||||
if (using_shm) {
|
||||
RTC_LOG(LS_VERBOSE)
|
||||
<< "Using X shared memory segment " << shm_segment_info_->shmid;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
RTC_LOG(LS_WARNING) << "Failed to get shared memory segment. "
|
||||
"Performance may be degraded.";
|
||||
}
|
||||
}
|
||||
|
||||
if (!using_shm) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "Not using shared memory. Performance may be degraded.";
|
||||
ReleaseSharedMemorySegment();
|
||||
return;
|
||||
}
|
||||
|
||||
if (have_pixmaps)
|
||||
have_pixmaps = InitPixmaps(default_depth);
|
||||
|
||||
shmctl(shm_segment_info_->shmid, IPC_RMID, 0);
|
||||
shm_segment_info_->shmid = -1;
|
||||
|
||||
RTC_LOG(LS_VERBOSE) << "Using X shared memory extension v" << major << "."
|
||||
<< minor << " with" << (have_pixmaps ? "" : "out")
|
||||
<< " pixmaps.";
|
||||
}
|
||||
|
||||
bool XServerPixelBuffer::InitPixmaps(int depth) {
|
||||
if (XShmPixmapFormat(display_) != ZPixmap)
|
||||
return false;
|
||||
|
||||
{
|
||||
XErrorTrap error_trap(display_);
|
||||
shm_pixmap_ = XShmCreatePixmap(
|
||||
display_, window_, shm_segment_info_->shmaddr, shm_segment_info_,
|
||||
window_rect_.width(), window_rect_.height(), depth);
|
||||
XSync(display_, False);
|
||||
if (error_trap.GetLastErrorAndDisable() != 0) {
|
||||
// `shm_pixmap_` is not not valid because the request was not processed
|
||||
// by the X Server, so zero it.
|
||||
shm_pixmap_ = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
XErrorTrap error_trap(display_);
|
||||
XGCValues shm_gc_values;
|
||||
shm_gc_values.subwindow_mode = IncludeInferiors;
|
||||
shm_gc_values.graphics_exposures = False;
|
||||
shm_gc_ = XCreateGC(display_, window_,
|
||||
GCSubwindowMode | GCGraphicsExposures, &shm_gc_values);
|
||||
XSync(display_, False);
|
||||
if (error_trap.GetLastErrorAndDisable() != 0) {
|
||||
XFreePixmap(display_, shm_pixmap_);
|
||||
shm_pixmap_ = 0;
|
||||
shm_gc_ = 0; // See shm_pixmap_ comment above.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XServerPixelBuffer::IsWindowValid() const {
|
||||
XWindowAttributes attributes;
|
||||
{
|
||||
XErrorTrap error_trap(display_);
|
||||
if (!XGetWindowAttributes(display_, window_, &attributes) ||
|
||||
error_trap.GetLastErrorAndDisable() != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void XServerPixelBuffer::Synchronize() {
|
||||
if (shm_segment_info_ && !shm_pixmap_) {
|
||||
// XShmGetImage can fail if the display is being reconfigured.
|
||||
XErrorTrap error_trap(display_);
|
||||
// XShmGetImage fails if the window is partially out of screen.
|
||||
xshm_get_image_succeeded_ =
|
||||
XShmGetImage(display_, window_, x_shm_image_, 0, 0, AllPlanes);
|
||||
}
|
||||
}
|
||||
|
||||
bool XServerPixelBuffer::CaptureRect(const DesktopRect& rect,
|
||||
DesktopFrame* frame) {
|
||||
RTC_DCHECK_LE(rect.right(), window_rect_.width());
|
||||
RTC_DCHECK_LE(rect.bottom(), window_rect_.height());
|
||||
|
||||
XImage* image;
|
||||
uint8_t* data;
|
||||
|
||||
if (shm_segment_info_ && (shm_pixmap_ || xshm_get_image_succeeded_)) {
|
||||
if (shm_pixmap_) {
|
||||
XCopyArea(display_, window_, shm_pixmap_, shm_gc_, rect.left(),
|
||||
rect.top(), rect.width(), rect.height(), rect.left(),
|
||||
rect.top());
|
||||
XSync(display_, False);
|
||||
}
|
||||
|
||||
image = x_shm_image_;
|
||||
data = reinterpret_cast<uint8_t*>(image->data) +
|
||||
rect.top() * image->bytes_per_line +
|
||||
rect.left() * image->bits_per_pixel / 8;
|
||||
|
||||
} else {
|
||||
if (x_image_)
|
||||
XDestroyImage(x_image_);
|
||||
x_image_ = XGetImage(display_, window_, rect.left(), rect.top(),
|
||||
rect.width(), rect.height(), AllPlanes, ZPixmap);
|
||||
if (!x_image_)
|
||||
return false;
|
||||
|
||||
image = x_image_;
|
||||
data = reinterpret_cast<uint8_t*>(image->data);
|
||||
}
|
||||
|
||||
if (IsXImageRGBFormat(image)) {
|
||||
FastBlit(image, data, rect, frame);
|
||||
} else {
|
||||
SlowBlit(image, data, rect, frame);
|
||||
}
|
||||
|
||||
if (!icc_profile_.empty())
|
||||
frame->set_icc_profile(icc_profile_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// Don't include this file in any .h files because it pulls in some X headers.
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_
|
||||
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/extensions/XShm.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class DesktopFrame;
|
||||
class XAtomCache;
|
||||
|
||||
// A class to allow the X server's pixel buffer to be accessed as efficiently
|
||||
// as possible.
|
||||
class XServerPixelBuffer {
|
||||
public:
|
||||
XServerPixelBuffer();
|
||||
~XServerPixelBuffer();
|
||||
|
||||
XServerPixelBuffer(const XServerPixelBuffer&) = delete;
|
||||
XServerPixelBuffer& operator=(const XServerPixelBuffer&) = delete;
|
||||
|
||||
void Release();
|
||||
|
||||
// Allocate (or reallocate) the pixel buffer for `window`. Returns false in
|
||||
// case of an error (e.g. window doesn't exist).
|
||||
bool Init(XAtomCache* cache, Window window);
|
||||
|
||||
bool is_initialized() { return window_ != 0; }
|
||||
|
||||
// Returns the size of the window the buffer was initialized for.
|
||||
DesktopSize window_size() { return window_rect_.size(); }
|
||||
|
||||
// Returns the rectangle of the window the buffer was initialized for.
|
||||
const DesktopRect& window_rect() { return window_rect_; }
|
||||
|
||||
// Returns true if the window can be found.
|
||||
bool IsWindowValid() const;
|
||||
|
||||
// If shared memory is being used without pixmaps, synchronize this pixel
|
||||
// buffer with the root window contents (otherwise, this is a no-op).
|
||||
// This is to avoid doing a full-screen capture for each individual
|
||||
// rectangle in the capture list, when it only needs to be done once at the
|
||||
// beginning.
|
||||
void Synchronize();
|
||||
|
||||
// Capture the specified rectangle and stores it in the `frame`. In the case
|
||||
// where the full-screen data is captured by Synchronize(), this simply
|
||||
// returns the pointer without doing any more work. The caller must ensure
|
||||
// that `rect` is not larger than window_size().
|
||||
bool CaptureRect(const DesktopRect& rect, DesktopFrame* frame);
|
||||
|
||||
private:
|
||||
void ReleaseSharedMemorySegment();
|
||||
|
||||
void InitShm(const XWindowAttributes& attributes);
|
||||
bool InitPixmaps(int depth);
|
||||
|
||||
Display* display_ = nullptr;
|
||||
Window window_ = 0;
|
||||
DesktopRect window_rect_;
|
||||
XImage* x_image_ = nullptr;
|
||||
XShmSegmentInfo* shm_segment_info_ = nullptr;
|
||||
XImage* x_shm_image_ = nullptr;
|
||||
Pixmap shm_pixmap_ = 0;
|
||||
GC shm_gc_ = nullptr;
|
||||
bool xshm_get_image_succeeded_ = false;
|
||||
std::vector<uint8_t> icc_profile_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_SERVER_PIXEL_BUFFER_H_
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/linux/x11/x_window_property.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
XWindowPropertyBase::XWindowPropertyBase(Display* display,
|
||||
Window window,
|
||||
Atom property,
|
||||
int expected_size) {
|
||||
const int kBitsPerByte = 8;
|
||||
Atom actual_type;
|
||||
int actual_format;
|
||||
unsigned long bytes_after; // NOLINT: type required by XGetWindowProperty
|
||||
int status = XGetWindowProperty(display, window, property, 0L, ~0L, False,
|
||||
AnyPropertyType, &actual_type, &actual_format,
|
||||
&size_, &bytes_after, &data_);
|
||||
if (status != Success) {
|
||||
data_ = nullptr;
|
||||
return;
|
||||
}
|
||||
if ((expected_size * kBitsPerByte) != actual_format) {
|
||||
size_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
is_valid_ = true;
|
||||
}
|
||||
|
||||
XWindowPropertyBase::~XWindowPropertyBase() {
|
||||
if (data_)
|
||||
XFree(data_);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_X11_X_WINDOW_PROPERTY_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_LINUX_X11_X_WINDOW_PROPERTY_H_
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class XWindowPropertyBase {
|
||||
public:
|
||||
XWindowPropertyBase(Display* display,
|
||||
Window window,
|
||||
Atom property,
|
||||
int expected_size);
|
||||
virtual ~XWindowPropertyBase();
|
||||
|
||||
XWindowPropertyBase(const XWindowPropertyBase&) = delete;
|
||||
XWindowPropertyBase& operator=(const XWindowPropertyBase&) = delete;
|
||||
|
||||
// True if we got properly value successfully.
|
||||
bool is_valid() const { return is_valid_; }
|
||||
|
||||
// Size and value of the property.
|
||||
size_t size() const { return size_; }
|
||||
|
||||
protected:
|
||||
unsigned char* data_ = nullptr;
|
||||
|
||||
private:
|
||||
bool is_valid_ = false;
|
||||
unsigned long size_ = 0; // NOLINT: type required by XGetWindowProperty
|
||||
};
|
||||
|
||||
// Convenience wrapper for XGetWindowProperty() results.
|
||||
template <class PropertyType>
|
||||
class XWindowProperty : public XWindowPropertyBase {
|
||||
public:
|
||||
XWindowProperty(Display* display, const Window window, const Atom property)
|
||||
: XWindowPropertyBase(display, window, property, sizeof(PropertyType)) {}
|
||||
~XWindowProperty() override = default;
|
||||
|
||||
XWindowProperty(const XWindowProperty&) = delete;
|
||||
XWindowProperty& operator=(const XWindowProperty&) = delete;
|
||||
|
||||
const PropertyType* data() const {
|
||||
return reinterpret_cast<PropertyType*>(data_);
|
||||
}
|
||||
PropertyType* data() { return reinterpret_cast<PropertyType*>(data_); }
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_LINUX_X11_X_WINDOW_PROPERTY_H_
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "modules/desktop_capture/desktop_geometry.h"
|
||||
#include "rtc_base/system/rtc_export.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Describes the configuration of a specific display.
|
||||
struct MacDisplayConfiguration {
|
||||
MacDisplayConfiguration();
|
||||
MacDisplayConfiguration(const MacDisplayConfiguration& other);
|
||||
MacDisplayConfiguration(MacDisplayConfiguration&& other);
|
||||
~MacDisplayConfiguration();
|
||||
|
||||
MacDisplayConfiguration& operator=(const MacDisplayConfiguration& other);
|
||||
MacDisplayConfiguration& operator=(MacDisplayConfiguration&& other);
|
||||
|
||||
// Cocoa identifier for this display.
|
||||
CGDirectDisplayID id = 0;
|
||||
|
||||
// Bounds of this display in Density-Independent Pixels (DIPs).
|
||||
DesktopRect bounds;
|
||||
|
||||
// Bounds of this display in physical pixels.
|
||||
DesktopRect pixel_bounds;
|
||||
|
||||
// Scale factor from DIPs to physical pixels.
|
||||
float dip_to_pixel_scale = 1.0f;
|
||||
|
||||
// Display type, built-in or external.
|
||||
bool is_builtin;
|
||||
};
|
||||
|
||||
typedef std::vector<MacDisplayConfiguration> MacDisplayConfigurations;
|
||||
|
||||
// Describes the configuration of the whole desktop.
|
||||
struct RTC_EXPORT MacDesktopConfiguration {
|
||||
// Used to request bottom-up or top-down coordinates.
|
||||
enum Origin { BottomLeftOrigin, TopLeftOrigin };
|
||||
|
||||
MacDesktopConfiguration();
|
||||
MacDesktopConfiguration(const MacDesktopConfiguration& other);
|
||||
MacDesktopConfiguration(MacDesktopConfiguration&& other);
|
||||
~MacDesktopConfiguration();
|
||||
|
||||
MacDesktopConfiguration& operator=(const MacDesktopConfiguration& other);
|
||||
MacDesktopConfiguration& operator=(MacDesktopConfiguration&& other);
|
||||
|
||||
// Returns the desktop & display configurations.
|
||||
// If BottomLeftOrigin is used, the output is in Cocoa-style "bottom-up"
|
||||
// (the origin is the bottom-left of the primary monitor, and coordinates
|
||||
// increase as you move up the screen). Otherwise, the configuration will be
|
||||
// converted to follow top-left coordinate system as Windows and X11.
|
||||
static MacDesktopConfiguration GetCurrent(Origin origin);
|
||||
|
||||
// Returns true if the given desktop configuration equals this one.
|
||||
bool Equals(const MacDesktopConfiguration& other);
|
||||
|
||||
// If `id` corresponds to the built-in display, return its configuration,
|
||||
// otherwise return the configuration for the display with the specified id,
|
||||
// or nullptr if no such display exists.
|
||||
const MacDisplayConfiguration* FindDisplayConfigurationById(
|
||||
CGDirectDisplayID id);
|
||||
|
||||
// Bounds of the desktop excluding monitors with DPI settings different from
|
||||
// the main monitor. In Density-Independent Pixels (DIPs).
|
||||
DesktopRect bounds;
|
||||
|
||||
// Same as bounds, but expressed in physical pixels.
|
||||
DesktopRect pixel_bounds;
|
||||
|
||||
// Scale factor from DIPs to physical pixels.
|
||||
float dip_to_pixel_scale = 1.0f;
|
||||
|
||||
// Configurations of the displays making up the desktop area.
|
||||
MacDisplayConfigurations displays;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_H_
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/mac/desktop_configuration.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
#if !defined(MAC_OS_X_VERSION_10_7) || \
|
||||
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
|
||||
|
||||
@interface NSScreen (LionAPI)
|
||||
- (CGFloat)backingScaleFactor;
|
||||
- (NSRect)convertRectToBacking:(NSRect)aRect;
|
||||
@end
|
||||
|
||||
#endif // MAC_OS_X_VERSION_10_7
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
DesktopRect NSRectToDesktopRect(const NSRect& ns_rect) {
|
||||
return DesktopRect::MakeLTRB(
|
||||
static_cast<int>(floor(ns_rect.origin.x)),
|
||||
static_cast<int>(floor(ns_rect.origin.y)),
|
||||
static_cast<int>(ceil(ns_rect.origin.x + ns_rect.size.width)),
|
||||
static_cast<int>(ceil(ns_rect.origin.y + ns_rect.size.height)));
|
||||
}
|
||||
|
||||
// Inverts the position of `rect` from bottom-up coordinates to top-down,
|
||||
// relative to `bounds`.
|
||||
void InvertRectYOrigin(const DesktopRect& bounds,
|
||||
DesktopRect* rect) {
|
||||
RTC_DCHECK_EQ(bounds.top(), 0);
|
||||
*rect = DesktopRect::MakeXYWH(
|
||||
rect->left(), bounds.bottom() - rect->bottom(),
|
||||
rect->width(), rect->height());
|
||||
}
|
||||
|
||||
MacDisplayConfiguration GetConfigurationForScreen(NSScreen* screen) {
|
||||
MacDisplayConfiguration display_config;
|
||||
|
||||
// Fetch the NSScreenNumber, which is also the CGDirectDisplayID.
|
||||
NSDictionary* device_description = [screen deviceDescription];
|
||||
display_config.id = static_cast<CGDirectDisplayID>(
|
||||
[[device_description objectForKey:@"NSScreenNumber"] intValue]);
|
||||
|
||||
// Determine the display's logical & physical dimensions.
|
||||
NSRect ns_bounds = [screen frame];
|
||||
display_config.bounds = NSRectToDesktopRect(ns_bounds);
|
||||
|
||||
// If the host is running Mac OS X 10.7+ or later, query the scaling factor
|
||||
// between logical and physical (aka "backing") pixels, otherwise assume 1:1.
|
||||
if ([screen respondsToSelector:@selector(backingScaleFactor)] &&
|
||||
[screen respondsToSelector:@selector(convertRectToBacking:)]) {
|
||||
display_config.dip_to_pixel_scale = [screen backingScaleFactor];
|
||||
NSRect ns_pixel_bounds = [screen convertRectToBacking: ns_bounds];
|
||||
display_config.pixel_bounds = NSRectToDesktopRect(ns_pixel_bounds);
|
||||
} else {
|
||||
display_config.pixel_bounds = display_config.bounds;
|
||||
}
|
||||
|
||||
// Determine if the display is built-in or external.
|
||||
display_config.is_builtin = CGDisplayIsBuiltin(display_config.id);
|
||||
|
||||
return display_config;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MacDisplayConfiguration::MacDisplayConfiguration() = default;
|
||||
MacDisplayConfiguration::MacDisplayConfiguration(
|
||||
const MacDisplayConfiguration& other) = default;
|
||||
MacDisplayConfiguration::MacDisplayConfiguration(
|
||||
MacDisplayConfiguration&& other) = default;
|
||||
MacDisplayConfiguration::~MacDisplayConfiguration() = default;
|
||||
|
||||
MacDisplayConfiguration& MacDisplayConfiguration::operator=(
|
||||
const MacDisplayConfiguration& other) = default;
|
||||
MacDisplayConfiguration& MacDisplayConfiguration::operator=(
|
||||
MacDisplayConfiguration&& other) = default;
|
||||
|
||||
MacDesktopConfiguration::MacDesktopConfiguration() = default;
|
||||
MacDesktopConfiguration::MacDesktopConfiguration(
|
||||
const MacDesktopConfiguration& other) = default;
|
||||
MacDesktopConfiguration::MacDesktopConfiguration(
|
||||
MacDesktopConfiguration&& other) = default;
|
||||
MacDesktopConfiguration::~MacDesktopConfiguration() = default;
|
||||
|
||||
MacDesktopConfiguration& MacDesktopConfiguration::operator=(
|
||||
const MacDesktopConfiguration& other) = default;
|
||||
MacDesktopConfiguration& MacDesktopConfiguration::operator=(
|
||||
MacDesktopConfiguration&& other) = default;
|
||||
|
||||
// static
|
||||
MacDesktopConfiguration MacDesktopConfiguration::GetCurrent(Origin origin) {
|
||||
MacDesktopConfiguration desktop_config;
|
||||
|
||||
NSArray* screens = [NSScreen screens];
|
||||
RTC_DCHECK(screens);
|
||||
|
||||
// Iterator over the monitors, adding the primary monitor and monitors whose
|
||||
// DPI match that of the primary monitor.
|
||||
for (NSUInteger i = 0; i < [screens count]; ++i) {
|
||||
MacDisplayConfiguration display_config =
|
||||
GetConfigurationForScreen([screens objectAtIndex: i]);
|
||||
|
||||
if (i == 0)
|
||||
desktop_config.dip_to_pixel_scale = display_config.dip_to_pixel_scale;
|
||||
|
||||
// Cocoa uses bottom-up coordinates, so if the caller wants top-down then
|
||||
// we need to invert the positions of secondary monitors relative to the
|
||||
// primary one (the primary monitor's position is (0,0) in both systems).
|
||||
if (i > 0 && origin == TopLeftOrigin) {
|
||||
InvertRectYOrigin(desktop_config.displays[0].bounds,
|
||||
&display_config.bounds);
|
||||
// `display_bounds` is density dependent, so we need to convert the
|
||||
// primay monitor's position into the secondary monitor's density context.
|
||||
float scaling_factor = display_config.dip_to_pixel_scale /
|
||||
desktop_config.displays[0].dip_to_pixel_scale;
|
||||
DesktopRect primary_bounds = DesktopRect::MakeLTRB(
|
||||
desktop_config.displays[0].pixel_bounds.left() * scaling_factor,
|
||||
desktop_config.displays[0].pixel_bounds.top() * scaling_factor,
|
||||
desktop_config.displays[0].pixel_bounds.right() * scaling_factor,
|
||||
desktop_config.displays[0].pixel_bounds.bottom() * scaling_factor);
|
||||
InvertRectYOrigin(primary_bounds, &display_config.pixel_bounds);
|
||||
}
|
||||
|
||||
// Add the display to the configuration.
|
||||
desktop_config.displays.push_back(display_config);
|
||||
|
||||
// Update the desktop bounds to account for this display, unless the current
|
||||
// display uses different DPI settings.
|
||||
if (display_config.dip_to_pixel_scale ==
|
||||
desktop_config.dip_to_pixel_scale) {
|
||||
desktop_config.bounds.UnionWith(display_config.bounds);
|
||||
desktop_config.pixel_bounds.UnionWith(display_config.pixel_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
return desktop_config;
|
||||
}
|
||||
|
||||
// For convenience of comparing MacDisplayConfigurations in
|
||||
// MacDesktopConfiguration::Equals.
|
||||
bool operator==(const MacDisplayConfiguration& left,
|
||||
const MacDisplayConfiguration& right) {
|
||||
return left.id == right.id &&
|
||||
left.bounds.equals(right.bounds) &&
|
||||
left.pixel_bounds.equals(right.pixel_bounds) &&
|
||||
left.dip_to_pixel_scale == right.dip_to_pixel_scale;
|
||||
}
|
||||
|
||||
bool MacDesktopConfiguration::Equals(const MacDesktopConfiguration& other) {
|
||||
return bounds.equals(other.bounds) &&
|
||||
pixel_bounds.equals(other.pixel_bounds) &&
|
||||
dip_to_pixel_scale == other.dip_to_pixel_scale &&
|
||||
displays == other.displays;
|
||||
}
|
||||
|
||||
const MacDisplayConfiguration*
|
||||
MacDesktopConfiguration::FindDisplayConfigurationById(
|
||||
CGDirectDisplayID id) {
|
||||
bool is_builtin = CGDisplayIsBuiltin(id);
|
||||
for (MacDisplayConfigurations::const_iterator it = displays.begin();
|
||||
it != displays.end(); ++it) {
|
||||
// The MBP having both discrete and integrated graphic cards will do
|
||||
// automate graphics switching by default. When it switches from discrete to
|
||||
// integrated one, the current display ID of the built-in display will
|
||||
// change and this will cause screen capture stops.
|
||||
// So make screen capture of built-in display continuing even if its display
|
||||
// ID is changed.
|
||||
if ((is_builtin && it->is_builtin) || (!is_builtin && it->id == id)) return &(*it);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/mac/desktop_configuration_monitor.h"
|
||||
|
||||
#include "modules/desktop_capture/mac/desktop_configuration.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/trace_event.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DesktopConfigurationMonitor::DesktopConfigurationMonitor() {
|
||||
CGError err = CGDisplayRegisterReconfigurationCallback(
|
||||
DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this);
|
||||
if (err != kCGErrorSuccess)
|
||||
RTC_LOG(LS_ERROR) << "CGDisplayRegisterReconfigurationCallback " << err;
|
||||
MutexLock lock(&desktop_configuration_lock_);
|
||||
desktop_configuration_ = MacDesktopConfiguration::GetCurrent(
|
||||
MacDesktopConfiguration::TopLeftOrigin);
|
||||
}
|
||||
|
||||
DesktopConfigurationMonitor::~DesktopConfigurationMonitor() {
|
||||
CGError err = CGDisplayRemoveReconfigurationCallback(
|
||||
DesktopConfigurationMonitor::DisplaysReconfiguredCallback, this);
|
||||
if (err != kCGErrorSuccess)
|
||||
RTC_LOG(LS_ERROR) << "CGDisplayRemoveReconfigurationCallback " << err;
|
||||
}
|
||||
|
||||
MacDesktopConfiguration DesktopConfigurationMonitor::desktop_configuration() {
|
||||
MutexLock lock(&desktop_configuration_lock_);
|
||||
return desktop_configuration_;
|
||||
}
|
||||
|
||||
// static
|
||||
// This method may be called on any system thread.
|
||||
void DesktopConfigurationMonitor::DisplaysReconfiguredCallback(
|
||||
CGDirectDisplayID display,
|
||||
CGDisplayChangeSummaryFlags flags,
|
||||
void* user_parameter) {
|
||||
DesktopConfigurationMonitor* monitor =
|
||||
reinterpret_cast<DesktopConfigurationMonitor*>(user_parameter);
|
||||
monitor->DisplaysReconfigured(display, flags);
|
||||
}
|
||||
|
||||
void DesktopConfigurationMonitor::DisplaysReconfigured(
|
||||
CGDirectDisplayID display,
|
||||
CGDisplayChangeSummaryFlags flags) {
|
||||
TRACE_EVENT0("webrtc", "DesktopConfigurationMonitor::DisplaysReconfigured");
|
||||
RTC_LOG(LS_INFO) << "DisplaysReconfigured: "
|
||||
"DisplayID "
|
||||
<< display << "; ChangeSummaryFlags " << flags;
|
||||
|
||||
if (flags & kCGDisplayBeginConfigurationFlag) {
|
||||
reconfiguring_displays_.insert(display);
|
||||
return;
|
||||
}
|
||||
|
||||
reconfiguring_displays_.erase(display);
|
||||
if (reconfiguring_displays_.empty()) {
|
||||
MutexLock lock(&desktop_configuration_lock_);
|
||||
desktop_configuration_ = MacDesktopConfiguration::GetCurrent(
|
||||
MacDesktopConfiguration::TopLeftOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_
|
||||
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include "api/ref_counted_base.h"
|
||||
#include "modules/desktop_capture/mac/desktop_configuration.h"
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// The class provides functions to synchronize capturing and display
|
||||
// reconfiguring across threads, and the up-to-date MacDesktopConfiguration.
|
||||
class DesktopConfigurationMonitor final
|
||||
: public rtc::RefCountedNonVirtual<DesktopConfigurationMonitor> {
|
||||
public:
|
||||
DesktopConfigurationMonitor();
|
||||
~DesktopConfigurationMonitor();
|
||||
|
||||
DesktopConfigurationMonitor(const DesktopConfigurationMonitor&) = delete;
|
||||
DesktopConfigurationMonitor& operator=(const DesktopConfigurationMonitor&) =
|
||||
delete;
|
||||
|
||||
// Returns the current desktop configuration.
|
||||
MacDesktopConfiguration desktop_configuration();
|
||||
|
||||
private:
|
||||
static void DisplaysReconfiguredCallback(CGDirectDisplayID display,
|
||||
CGDisplayChangeSummaryFlags flags,
|
||||
void* user_parameter);
|
||||
void DisplaysReconfigured(CGDirectDisplayID display,
|
||||
CGDisplayChangeSummaryFlags flags);
|
||||
|
||||
Mutex desktop_configuration_lock_;
|
||||
MacDesktopConfiguration desktop_configuration_
|
||||
RTC_GUARDED_BY(&desktop_configuration_lock_);
|
||||
std::set<CGDirectDisplayID> reconfiguring_displays_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_CONFIGURATION_MONITOR_H_
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "sdk/objc/helpers/scoped_cftyperef.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class RTC_EXPORT DesktopFrameCGImage final : public DesktopFrame {
|
||||
public:
|
||||
// Create an image containing a snapshot of the display at the time this is
|
||||
// being called.
|
||||
static std::unique_ptr<DesktopFrameCGImage> CreateForDisplay(
|
||||
CGDirectDisplayID display_id);
|
||||
|
||||
// Create an image containing a snaphot of the given window at the time this
|
||||
// is being called. This also works when the window is overlapped or in
|
||||
// another workspace.
|
||||
static std::unique_ptr<DesktopFrameCGImage> CreateForWindow(
|
||||
CGWindowID window_id);
|
||||
|
||||
static std::unique_ptr<DesktopFrameCGImage> CreateFromCGImage(
|
||||
rtc::ScopedCFTypeRef<CGImageRef> cg_image);
|
||||
|
||||
~DesktopFrameCGImage() override;
|
||||
|
||||
DesktopFrameCGImage(const DesktopFrameCGImage&) = delete;
|
||||
DesktopFrameCGImage& operator=(const DesktopFrameCGImage&) = delete;
|
||||
|
||||
private:
|
||||
// This constructor expects `cg_image` to hold a non-null CGImageRef.
|
||||
DesktopFrameCGImage(DesktopSize size,
|
||||
int stride,
|
||||
uint8_t* data,
|
||||
rtc::ScopedCFTypeRef<CGImageRef> cg_image,
|
||||
rtc::ScopedCFTypeRef<CFDataRef> cg_data);
|
||||
|
||||
const rtc::ScopedCFTypeRef<CGImageRef> cg_image_;
|
||||
const rtc::ScopedCFTypeRef<CFDataRef> cg_data_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_CGIMAGE_H_
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/mac/desktop_frame_cgimage.h"
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateForDisplay(
|
||||
CGDirectDisplayID display_id) {
|
||||
// Create an image containing a snapshot of the display.
|
||||
rtc::ScopedCFTypeRef<CGImageRef> cg_image(CGDisplayCreateImage(display_id));
|
||||
if (!cg_image) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return DesktopFrameCGImage::CreateFromCGImage(cg_image);
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateForWindow(CGWindowID window_id) {
|
||||
rtc::ScopedCFTypeRef<CGImageRef> cg_image(
|
||||
CGWindowListCreateImage(CGRectNull,
|
||||
kCGWindowListOptionIncludingWindow,
|
||||
window_id,
|
||||
kCGWindowImageBoundsIgnoreFraming));
|
||||
if (!cg_image) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return DesktopFrameCGImage::CreateFromCGImage(cg_image);
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopFrameCGImage> DesktopFrameCGImage::CreateFromCGImage(
|
||||
rtc::ScopedCFTypeRef<CGImageRef> cg_image) {
|
||||
// Verify that the image has 32-bit depth.
|
||||
int bits_per_pixel = CGImageGetBitsPerPixel(cg_image.get());
|
||||
if (bits_per_pixel / 8 != DesktopFrame::kBytesPerPixel) {
|
||||
RTC_LOG(LS_ERROR) << "CGDisplayCreateImage() returned imaged with " << bits_per_pixel
|
||||
<< " bits per pixel. Only 32-bit depth is supported.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Request access to the raw pixel data via the image's DataProvider.
|
||||
CGDataProviderRef cg_provider = CGImageGetDataProvider(cg_image.get());
|
||||
RTC_DCHECK(cg_provider);
|
||||
|
||||
// CGDataProviderCopyData returns a new data object containing a copy of the provider’s
|
||||
// data.
|
||||
rtc::ScopedCFTypeRef<CFDataRef> cg_data(CGDataProviderCopyData(cg_provider));
|
||||
RTC_DCHECK(cg_data);
|
||||
|
||||
// CFDataGetBytePtr returns a read-only pointer to the bytes of a CFData object.
|
||||
uint8_t* data = const_cast<uint8_t*>(CFDataGetBytePtr(cg_data.get()));
|
||||
RTC_DCHECK(data);
|
||||
|
||||
DesktopSize size(CGImageGetWidth(cg_image.get()), CGImageGetHeight(cg_image.get()));
|
||||
int stride = CGImageGetBytesPerRow(cg_image.get());
|
||||
|
||||
std::unique_ptr<DesktopFrameCGImage> frame(
|
||||
new DesktopFrameCGImage(size, stride, data, cg_image, cg_data));
|
||||
|
||||
CGColorSpaceRef cg_color_space = CGImageGetColorSpace(cg_image.get());
|
||||
if (cg_color_space) {
|
||||
#if !defined(MAC_OS_X_VERSION_10_13) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_13
|
||||
rtc::ScopedCFTypeRef<CFDataRef> cf_icc_profile(CGColorSpaceCopyICCProfile(cg_color_space));
|
||||
#else
|
||||
rtc::ScopedCFTypeRef<CFDataRef> cf_icc_profile(CGColorSpaceCopyICCData(cg_color_space));
|
||||
#endif
|
||||
if (cf_icc_profile) {
|
||||
const uint8_t* data_as_byte =
|
||||
reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(cf_icc_profile.get()));
|
||||
const size_t data_size = CFDataGetLength(cf_icc_profile.get());
|
||||
if (data_as_byte && data_size > 0) {
|
||||
frame->set_icc_profile(std::vector<uint8_t>(data_as_byte, data_as_byte + data_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
DesktopFrameCGImage::DesktopFrameCGImage(DesktopSize size,
|
||||
int stride,
|
||||
uint8_t* data,
|
||||
rtc::ScopedCFTypeRef<CGImageRef> cg_image,
|
||||
rtc::ScopedCFTypeRef<CFDataRef> cg_data)
|
||||
: DesktopFrame(size, stride, data, nullptr), cg_image_(cg_image), cg_data_(cg_data) {
|
||||
RTC_DCHECK(cg_image_);
|
||||
RTC_DCHECK(cg_data_);
|
||||
}
|
||||
|
||||
DesktopFrameCGImage::~DesktopFrameCGImage() = default;
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <IOSurface/IOSurface.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/desktop_capture/desktop_frame.h"
|
||||
#include "sdk/objc/helpers/scoped_cftyperef.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class DesktopFrameIOSurface final : public DesktopFrame {
|
||||
public:
|
||||
// Lock an IOSurfaceRef containing a snapshot of a display. Return NULL if
|
||||
// failed to lock.
|
||||
static std::unique_ptr<DesktopFrameIOSurface> Wrap(
|
||||
rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface);
|
||||
|
||||
~DesktopFrameIOSurface() override;
|
||||
|
||||
DesktopFrameIOSurface(const DesktopFrameIOSurface&) = delete;
|
||||
DesktopFrameIOSurface& operator=(const DesktopFrameIOSurface&) = delete;
|
||||
|
||||
private:
|
||||
// This constructor expects `io_surface` to hold a non-null IOSurfaceRef.
|
||||
explicit DesktopFrameIOSurface(rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface);
|
||||
|
||||
const rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_IOSURFACE_H_
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/mac/desktop_frame_iosurface.h"
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// static
|
||||
std::unique_ptr<DesktopFrameIOSurface> DesktopFrameIOSurface::Wrap(
|
||||
rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) {
|
||||
if (!io_surface) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IOSurfaceIncrementUseCount(io_surface.get());
|
||||
IOReturn status = IOSurfaceLock(io_surface.get(), kIOSurfaceLockReadOnly, nullptr);
|
||||
if (status != kIOReturnSuccess) {
|
||||
RTC_LOG(LS_ERROR) << "Failed to lock the IOSurface with status " << status;
|
||||
IOSurfaceDecrementUseCount(io_surface.get());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Verify that the image has 32-bit depth.
|
||||
int bytes_per_pixel = IOSurfaceGetBytesPerElement(io_surface.get());
|
||||
if (bytes_per_pixel != DesktopFrame::kBytesPerPixel) {
|
||||
RTC_LOG(LS_ERROR) << "CGDisplayStream handler returned IOSurface with " << (8 * bytes_per_pixel)
|
||||
<< " bits per pixel. Only 32-bit depth is supported.";
|
||||
IOSurfaceUnlock(io_surface.get(), kIOSurfaceLockReadOnly, nullptr);
|
||||
IOSurfaceDecrementUseCount(io_surface.get());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::unique_ptr<DesktopFrameIOSurface>(new DesktopFrameIOSurface(io_surface));
|
||||
}
|
||||
|
||||
DesktopFrameIOSurface::DesktopFrameIOSurface(rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface)
|
||||
: DesktopFrame(
|
||||
DesktopSize(IOSurfaceGetWidth(io_surface.get()), IOSurfaceGetHeight(io_surface.get())),
|
||||
IOSurfaceGetBytesPerRow(io_surface.get()),
|
||||
static_cast<uint8_t*>(IOSurfaceGetBaseAddress(io_surface.get())),
|
||||
nullptr),
|
||||
io_surface_(io_surface) {
|
||||
RTC_DCHECK(io_surface_);
|
||||
}
|
||||
|
||||
DesktopFrameIOSurface::~DesktopFrameIOSurface() {
|
||||
IOSurfaceUnlock(io_surface_.get(), kIOSurfaceLockReadOnly, nullptr);
|
||||
IOSurfaceDecrementUseCount(io_surface_.get());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_
|
||||
#define MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <IOSurface/IOSurface.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "api/sequence_checker.h"
|
||||
#include "modules/desktop_capture/shared_desktop_frame.h"
|
||||
#include "sdk/objc/helpers/scoped_cftyperef.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class DesktopFrameProvider {
|
||||
public:
|
||||
explicit DesktopFrameProvider(bool allow_iosurface);
|
||||
~DesktopFrameProvider();
|
||||
|
||||
DesktopFrameProvider(const DesktopFrameProvider&) = delete;
|
||||
DesktopFrameProvider& operator=(const DesktopFrameProvider&) = delete;
|
||||
|
||||
// The caller takes ownership of the returned desktop frame. Otherwise
|
||||
// returns null if `display_id` is invalid or not ready. Note that this
|
||||
// function does not remove the frame from the internal container. Caller
|
||||
// has to call the Release function.
|
||||
std::unique_ptr<DesktopFrame> TakeLatestFrameForDisplay(
|
||||
CGDirectDisplayID display_id);
|
||||
|
||||
// OS sends the latest IOSurfaceRef through
|
||||
// CGDisplayStreamFrameAvailableHandler callback; we store it here.
|
||||
void InvalidateIOSurface(CGDirectDisplayID display_id,
|
||||
rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface);
|
||||
|
||||
// Expected to be called before stopping the CGDisplayStreamRef streams.
|
||||
void Release();
|
||||
|
||||
bool allow_iosurface() const { return allow_iosurface_; }
|
||||
|
||||
private:
|
||||
SequenceChecker thread_checker_;
|
||||
const bool allow_iosurface_;
|
||||
|
||||
// Most recent IOSurface that contains a capture of matching display.
|
||||
std::map<CGDirectDisplayID, std::unique_ptr<SharedDesktopFrame>> io_surfaces_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_DESKTOP_CAPTURE_MAC_DESKTOP_FRAME_PROVIDER_H_
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/mac/desktop_frame_provider.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "modules/desktop_capture/mac/desktop_frame_cgimage.h"
|
||||
#include "modules/desktop_capture/mac/desktop_frame_iosurface.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DesktopFrameProvider::DesktopFrameProvider(bool allow_iosurface)
|
||||
: allow_iosurface_(allow_iosurface) {
|
||||
thread_checker_.Detach();
|
||||
}
|
||||
|
||||
DesktopFrameProvider::~DesktopFrameProvider() {
|
||||
RTC_DCHECK(thread_checker_.IsCurrent());
|
||||
|
||||
Release();
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrame> DesktopFrameProvider::TakeLatestFrameForDisplay(
|
||||
CGDirectDisplayID display_id) {
|
||||
RTC_DCHECK(thread_checker_.IsCurrent());
|
||||
|
||||
if (!allow_iosurface_ || !io_surfaces_[display_id]) {
|
||||
// Regenerate a snapshot. If iosurface is on it will be empty until the
|
||||
// stream handler is called.
|
||||
return DesktopFrameCGImage::CreateForDisplay(display_id);
|
||||
}
|
||||
|
||||
return io_surfaces_[display_id]->Share();
|
||||
}
|
||||
|
||||
void DesktopFrameProvider::InvalidateIOSurface(CGDirectDisplayID display_id,
|
||||
rtc::ScopedCFTypeRef<IOSurfaceRef> io_surface) {
|
||||
RTC_DCHECK(thread_checker_.IsCurrent());
|
||||
|
||||
if (!allow_iosurface_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<DesktopFrameIOSurface> desktop_frame_iosurface =
|
||||
DesktopFrameIOSurface::Wrap(io_surface);
|
||||
|
||||
io_surfaces_[display_id] = desktop_frame_iosurface ?
|
||||
SharedDesktopFrame::Wrap(std::move(desktop_frame_iosurface)) :
|
||||
nullptr;
|
||||
}
|
||||
|
||||
void DesktopFrameProvider::Release() {
|
||||
RTC_DCHECK(thread_checker_.IsCurrent());
|
||||
|
||||
if (!allow_iosurface_) {
|
||||
return;
|
||||
}
|
||||
|
||||
io_surfaces_.clear();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/desktop_capture/mac/desktop_frame_utils.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::unique_ptr<DesktopFrame> CreateDesktopFrameFromCGImage(
|
||||
rtc::ScopedCFTypeRef<CGImageRef> cg_image) {
|
||||
return DesktopFrameCGImage::CreateFromCGImage(cg_image);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue