DxgiOutputDuplicator objects hold a reference to the last frame that they succesfully captured by maintaining a reference to the SharedDesktopFrame that was passed as their target. This is done because the DirectX capture APIs may fail to provide an update if there has been no (or no substantial) change since the last capture call was made. However, the higher levels of this capture stack (DxgiDuplicatorController and ScreenCapturerWinDirectX), were unaware of this, and assumed that the caller of CaptureFrame is the only one who may have held a reference to the frame. Thus, when CaptureFrame is called, the DirectX screen capturer assumes that the oldest frame in its queue can be safely reused. In the steady state, where capture is not being switched between monitors, this is fine as there are no competing DxgiOutputDuplicators being run and this assumption mostly holds true (or the frame is being overwritten only when the DxgiOutputDuplicator is also done holding it). However, when capture is being rapidly switched between multiple targets (e.g. to show a preview of each of the available monitors), this can result in a frame being held by one DxgiOutputDuplicator being passed to another as a valid target and overwritten. In the common case of only a single monitor this is essentially the same as steady state capture, where there are no competing DxgiOutputDuplicator. In the other common case of two monitors being captured, the fact that the ScreenCaptureFrameQueue has two frames ends up masking this issue. Since each monitor is captured in the same order, the same frame ends up getting passed to each DxgiOutputDuplicator, so no data actually ends up getting overwritten. In the case of 3 monitors, the 1st and 3rd monitor end up sharing a frame, which when capture fails on one of them surfaces as the other monitor being duplicately shown. This change addresses the issue by ensuring that each screen that the ScreenCapturerWinDirectX *actually attempts* to capture, gets it's own FrameQueue, and thus essentially brings us back to the "steady state" case for each monitor. Note that this does increase memory usage of capturers that are switched between multiple targets by 2 frames/target used (and actually attempted to be captured). Alternatives considered: DxgiOutputDuplicator makes a copy of the frame, rather than holding a reference This was rejected because adding an additional copy for every capture upon getting a new frame, would expensive and could degrade performance. Allow the DxgiOutputDuplicators to "fail" when there has been no update This would result in either a breaking change to the API for consumers or would require the ScreenCapturerWinDirectX to track these last captured frames; which would result in essentially the same approach, but with less abstraction for re-using the frames. Bug: chromium:1296228 Change-Id: I5442ec40e9f234046010b562b258db63693ccc6b Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/256043 Reviewed-by: Mark Foltz <mfoltz@chromium.org> Commit-Queue: Alexander Cooper <alcooper@chromium.org> Cr-Commit-Position: refs/heads/main@{#36295}
106 lines
4.4 KiB
C++
106 lines
4.4 KiB
C++
/*
|
|
* 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_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_
|
|
#define MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_
|
|
|
|
#include <d3dcommon.h>
|
|
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#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_region.h"
|
|
#include "modules/desktop_capture/screen_capture_frame_queue.h"
|
|
#include "modules/desktop_capture/win/dxgi_duplicator_controller.h"
|
|
#include "modules/desktop_capture/win/dxgi_frame.h"
|
|
#include "rtc_base/system/rtc_export.h"
|
|
|
|
namespace webrtc {
|
|
|
|
// ScreenCapturerWinDirectx captures 32bit RGBA using DirectX.
|
|
class RTC_EXPORT ScreenCapturerWinDirectx : public DesktopCapturer {
|
|
public:
|
|
using D3dInfo = DxgiDuplicatorController::D3dInfo;
|
|
|
|
// Whether the system supports DirectX based capturing.
|
|
static bool IsSupported();
|
|
|
|
// Returns a most recent D3dInfo composed by
|
|
// DxgiDuplicatorController::Initialize() function. This function implicitly
|
|
// calls DxgiDuplicatorController::Initialize() if it has not been
|
|
// initialized. This function returns false and output parameter is kept
|
|
// unchanged if DxgiDuplicatorController::Initialize() failed.
|
|
// The D3dInfo may change based on hardware configuration even without
|
|
// restarting the hardware and software. Refer to https://goo.gl/OOCppq. So
|
|
// consumers should not cache the result returned by this function.
|
|
static bool RetrieveD3dInfo(D3dInfo* info);
|
|
|
|
// Whether current process is running in a Windows session which is supported
|
|
// by ScreenCapturerWinDirectx.
|
|
// Usually using ScreenCapturerWinDirectx in unsupported sessions will fail.
|
|
// But this behavior may vary on different Windows version. So consumers can
|
|
// always try IsSupported() function.
|
|
static bool IsCurrentSessionSupported();
|
|
|
|
// Maps `device_names` with the result from GetScreenList() and creates a new
|
|
// SourceList to include only the ones in `device_names`. If this function
|
|
// returns true, consumers can always assume `device_names`.size() equals to
|
|
// `screens`->size(), meanwhile `device_names`[i] and `screens`[i] indicate
|
|
// the same monitor on the system.
|
|
// Public for test only.
|
|
static bool GetScreenListFromDeviceNames(
|
|
const std::vector<std::string>& device_names,
|
|
DesktopCapturer::SourceList* screens);
|
|
|
|
// Maps `id` with the result from GetScreenListFromDeviceNames() and returns
|
|
// the index of the entity in `device_names`. This function returns -1 if `id`
|
|
// cannot be found.
|
|
// Public for test only.
|
|
static int GetIndexFromScreenId(ScreenId id,
|
|
const std::vector<std::string>& device_names);
|
|
|
|
explicit ScreenCapturerWinDirectx();
|
|
|
|
~ScreenCapturerWinDirectx() override;
|
|
|
|
ScreenCapturerWinDirectx(const ScreenCapturerWinDirectx&) = delete;
|
|
ScreenCapturerWinDirectx& operator=(const ScreenCapturerWinDirectx&) = delete;
|
|
|
|
// DesktopCapturer implementation.
|
|
void Start(Callback* callback) override;
|
|
void SetSharedMemoryFactory(
|
|
std::unique_ptr<SharedMemoryFactory> shared_memory_factory) override;
|
|
void CaptureFrame() override;
|
|
bool GetSourceList(SourceList* sources) override;
|
|
bool SelectSource(SourceId id) override;
|
|
|
|
private:
|
|
const rtc::scoped_refptr<DxgiDuplicatorController> controller_;
|
|
|
|
// The underlying DxgiDuplicators may retain a reference to the frames that
|
|
// we ask them to duplicate so that they can continue returning valid frames
|
|
// in the event that the target has not been updated. Thus, we need to ensure
|
|
// that we have a separate frame queue for each source id, so that these held
|
|
// frames don't get overwritten with the data from another Duplicator/monitor.
|
|
std::unordered_map<SourceId, ScreenCaptureFrameQueue<DxgiFrame>>
|
|
frame_queue_map_;
|
|
std::unique_ptr<SharedMemoryFactory> shared_memory_factory_;
|
|
Callback* callback_ = nullptr;
|
|
SourceId current_screen_id_ = kFullDesktopScreenId;
|
|
};
|
|
|
|
} // namespace webrtc
|
|
|
|
#endif // MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_DIRECTX_H_
|