Skip to content

Commit 1cde209

Browse files
committed
Add Windows capture backends for recording
1 parent caae5b5 commit 1cde209

6 files changed

Lines changed: 151 additions & 13 deletions

File tree

.gitlab-ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ windows-builder-x64:
105105
- $env:OpenCV_DIR = "$env:OPENCV_ROOT\lib\cmake\opencv4"
106106
- $env:Path = "$env:OPENCV_ROOT\bin;C:\msys64\mingw64\bin;C:\msys64\usr\bin;C:\msys64\usr\local\bin;" + $env:Path;
107107
- $env:MSYSTEM = "MINGW64"
108+
- ffmpeg -hide_banner -devices | findstr /I "gdigrab"
109+
- ffmpeg -hide_banner -devices | findstr /I "dshow"
108110
- cmake -B build -S . -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -D"babl_DIR=C:/msys64/mingw64" -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR\build\install-x64" -D"OpenShotAudio_ROOT=$CI_PROJECT_DIR\build\install-x64" -D"OpenCV_DIR:PATH=$env:OpenCV_DIR" -D"PYTHON_MODULE_PATH=python" -D"RUBY_MODULE_PATH=ruby" -G "MinGW Makefiles" -D"CMAKE_BUILD_TYPE:STRING=Release"
109111
- cmake --build build --parallel $([Environment]::ProcessorCount)
110112
- ctest --test-dir build --output-on-failure -VV
@@ -134,6 +136,8 @@ windows-builder-x86:
134136
- $env:OpenCV_DIR = "$env:OPENCV_ROOT\lib\cmake\opencv4"
135137
- $env:Path = "$env:OPENCV_ROOT\bin;C:\msys64\mingw32\bin;C:\msys64\usr\bin;C:\msys64\usr\local\bin;" + $env:Path;
136138
- $env:MSYSTEM = "MINGW32"
139+
- ffmpeg -hide_banner -devices | findstr /I "gdigrab"
140+
- ffmpeg -hide_banner -devices | findstr /I "dshow"
137141
- cmake -B build -S . -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -D"babl_DIR=C:/msys64/mingw32" -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR\build\install-x86" -D"OpenShotAudio_ROOT=$CI_PROJECT_DIR\build\install-x86" -D"OpenCV_DIR:PATH=$env:OpenCV_DIR" -D"PYTHON_MODULE_PATH=python" -D"RUBY_MODULE_PATH=ruby" -G "MinGW Makefiles" -D"CMAKE_BUILD_TYPE:STRING=Release" -D"CMAKE_CXX_FLAGS=-m32" -D"CMAKE_EXE_LINKER_FLAGS=-Wl,--large-address-aware" -D"CMAKE_C_FLAGS=-m32"
138142
- cmake --build build --parallel $([Environment]::ProcessorCount)
139143
- cmake --install build

src/CameraCaptureReader.cpp

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
#include <cstdlib>
1616

17+
extern "C" {
18+
#include <libavdevice/avdevice.h>
19+
}
20+
1721
#include "Exceptions.h"
1822

1923
using namespace openshot;
@@ -38,6 +42,8 @@ bool CameraCaptureReader::IsBackendSupported(CameraCaptureBackend backend)
3842
{
3943
#if defined(__linux__)
4044
return backend == CAMERA_CAPTURE_V4L2 || backend == CAMERA_CAPTURE_AUTO;
45+
#elif defined(_WIN32)
46+
return backend == CAMERA_CAPTURE_WINDOWS_DSHOW || backend == CAMERA_CAPTURE_AUTO;
4147
#else
4248
(void) backend;
4349
return false;
@@ -48,18 +54,78 @@ CameraCaptureBackend CameraCaptureReader::DefaultBackend()
4854
{
4955
#if defined(__linux__)
5056
return CAMERA_CAPTURE_V4L2;
57+
#elif defined(_WIN32)
58+
return CAMERA_CAPTURE_WINDOWS_DSHOW;
5159
#else
5260
return CAMERA_CAPTURE_AUTO;
5361
#endif
5462
}
5563

64+
AudioDeviceList CameraCaptureReader::GetDeviceNames(CameraCaptureBackend backend)
65+
{
66+
if (backend == CAMERA_CAPTURE_AUTO) {
67+
backend = DefaultBackend();
68+
}
69+
70+
AudioDeviceList devices;
71+
const char* input_format_name = nullptr;
72+
#if defined(__linux__)
73+
if (backend == CAMERA_CAPTURE_V4L2) {
74+
input_format_name = "v4l2";
75+
}
76+
#elif defined(_WIN32)
77+
if (backend == CAMERA_CAPTURE_WINDOWS_DSHOW) {
78+
input_format_name = "dshow";
79+
}
80+
#endif
81+
if (!input_format_name) {
82+
return devices;
83+
}
84+
85+
avdevice_register_all();
86+
const AVInputFormat* input_format = av_find_input_format(input_format_name);
87+
if (!input_format) {
88+
return devices;
89+
}
90+
91+
AVDeviceInfoList* device_list = nullptr;
92+
const int result = avdevice_list_input_sources(input_format, nullptr, nullptr, &device_list);
93+
if (result >= 0 && device_list) {
94+
for (int index = 0; index < device_list->nb_devices; ++index) {
95+
const AVDeviceInfo* device = device_list->devices[index];
96+
if (!device || !device->device_name) {
97+
continue;
98+
}
99+
if (device->nb_media_types > 0 && device->media_types) {
100+
bool has_video = false;
101+
for (int media_index = 0; media_index < device->nb_media_types; ++media_index) {
102+
if (device->media_types[media_index] == AVMEDIA_TYPE_VIDEO) {
103+
has_video = true;
104+
break;
105+
}
106+
}
107+
if (!has_video) {
108+
continue;
109+
}
110+
}
111+
const std::string name = device->device_name;
112+
const std::string label = device->device_description
113+
? device->device_description
114+
: device->device_name;
115+
devices.emplace_back(label, name);
116+
}
117+
}
118+
avdevice_free_list_devices(&device_list);
119+
return devices;
120+
}
121+
56122
void CameraCaptureReader::ValidateSettings() const
57123
{
58124
if (!IsBackendSupported(settings.backend)) {
59125
throw InvalidOptions("Camera capture backend is not supported on this OS.");
60126
}
61-
if (settings.backend != CAMERA_CAPTURE_V4L2) {
62-
throw InvalidOptions("Only the v4l2 camera capture backend is implemented in this build.");
127+
if (settings.backend != CAMERA_CAPTURE_V4L2 && settings.backend != CAMERA_CAPTURE_WINDOWS_DSHOW) {
128+
throw InvalidOptions("Camera capture backend is not implemented in this build.");
63129
}
64130
if (settings.device.empty()) {
65131
throw InvalidOptions("Camera capture requires a device path.");
@@ -75,13 +141,19 @@ void CameraCaptureReader::ValidateSettings() const
75141
ScreenCaptureSettings CameraCaptureReader::ToDeviceSettings() const
76142
{
77143
ScreenCaptureSettings converted;
78-
converted.backend = SCREEN_CAPTURE_X11;
79144
converted.display = settings.device;
80145
converted.width = settings.width;
81146
converted.height = settings.height;
82147
converted.fps = settings.fps;
83148
converted.options = settings.options;
84-
converted.options["input_format_name"] = "v4l2";
149+
if (settings.backend == CAMERA_CAPTURE_WINDOWS_DSHOW) {
150+
converted.backend = SCREEN_CAPTURE_WINDOWS_GDI;
151+
converted.display = "video=" + settings.device;
152+
converted.options["input_format_name"] = "dshow";
153+
} else {
154+
converted.backend = SCREEN_CAPTURE_X11;
155+
converted.options["input_format_name"] = "v4l2";
156+
}
85157
return converted;
86158
}
87159

src/CameraCaptureReader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#ifndef OPENSHOT_CAMERACAPTUREREADER_H
1414
#define OPENSHOT_CAMERACAPTUREREADER_H
1515

16+
#include "AudioDevices.h"
1617
#include "ScreenCaptureReader.h"
1718

1819
#include <memory>
@@ -58,6 +59,7 @@ namespace openshot
5859
CameraCaptureSettings GetSettings() const { return settings; };
5960
static bool IsBackendSupported(CameraCaptureBackend backend);
6061
static CameraCaptureBackend DefaultBackend();
62+
static AudioDeviceList GetDeviceNames(CameraCaptureBackend backend = CAMERA_CAPTURE_AUTO);
6163

6264
private:
6365
void ValidateSettings() const;

src/ScreenCaptureReader.cpp

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ bool ScreenCaptureReader::IsBackendSupported(ScreenCaptureBackend backend)
100100
}
101101
#endif
102102
return false;
103+
#elif defined(_WIN32)
104+
return backend == SCREEN_CAPTURE_WINDOWS_GDI || backend == SCREEN_CAPTURE_AUTO;
103105
#else
104106
(void) backend;
105107
return false;
@@ -116,18 +118,19 @@ ScreenCaptureBackend ScreenCaptureReader::DefaultBackend()
116118
if (!session || std::string(session) == "x11") {
117119
return SCREEN_CAPTURE_X11;
118120
}
121+
#elif defined(_WIN32)
122+
return SCREEN_CAPTURE_WINDOWS_GDI;
119123
#endif
120124
return SCREEN_CAPTURE_AUTO;
121125
}
122126

123127
void ScreenCaptureReader::ValidateSettings() const
124128
{
125-
const bool using_v4l2_device = settings.options.count("input_format_name") && settings.options.at("input_format_name") == "v4l2";
126129
if (!IsBackendSupported(settings.backend)) {
127130
throw InvalidOptions("Screen capture backend is not supported on this OS or session.");
128131
}
129-
if (!UsesFFmpegDevice() && !UsesWaylandPortal() && !using_v4l2_device) {
130-
throw InvalidOptions("Only the X11 screen capture backend is implemented in this build.");
132+
if (!UsesFFmpegDevice() && !UsesWaylandPortal()) {
133+
throw InvalidOptions("Screen capture backend is not implemented in this build.");
131134
}
132135
if (settings.width <= 0 || settings.height <= 0) {
133136
throw InvalidOptions("Screen capture requires a positive width and height.");
@@ -139,8 +142,9 @@ void ScreenCaptureReader::ValidateSettings() const
139142

140143
bool ScreenCaptureReader::UsesFFmpegDevice() const
141144
{
142-
const bool using_v4l2_device = settings.options.count("input_format_name") && settings.options.at("input_format_name") == "v4l2";
143-
return settings.backend == SCREEN_CAPTURE_X11 || using_v4l2_device;
145+
return settings.backend == SCREEN_CAPTURE_X11
146+
|| settings.backend == SCREEN_CAPTURE_WINDOWS_GDI
147+
|| settings.options.count("input_format_name");
144148
}
145149

146150
bool ScreenCaptureReader::UsesWaylandPortal() const
@@ -157,6 +161,9 @@ std::string ScreenCaptureReader::InputFormatName() const
157161
if (settings.backend == SCREEN_CAPTURE_X11) {
158162
return "x11grab";
159163
}
164+
if (settings.backend == SCREEN_CAPTURE_WINDOWS_GDI) {
165+
return "gdigrab";
166+
}
160167
return "";
161168
}
162169

@@ -165,6 +172,12 @@ std::string ScreenCaptureReader::InputName() const
165172
if (InputFormatName() == "v4l2") {
166173
return settings.display.empty() ? "/dev/video0" : settings.display;
167174
}
175+
if (InputFormatName() == "dshow") {
176+
return settings.display;
177+
}
178+
if (InputFormatName() == "gdigrab") {
179+
return settings.display.empty() ? "desktop" : settings.display;
180+
}
168181

169182
std::string display = settings.display;
170183
if (display.empty()) {
@@ -239,6 +252,11 @@ void ScreenCaptureReader::OpenDevice()
239252
if (InputFormatName() == "x11grab") {
240253
set_option(&options, "draw_mouse", settings.include_cursor ? "1" : "0");
241254
set_option(&options, "show_region", settings.show_region ? "1" : "0");
255+
} else if (InputFormatName() == "gdigrab") {
256+
set_option(&options, "draw_mouse", settings.include_cursor ? "1" : "0");
257+
set_option(&options, "show_region", settings.show_region ? "1" : "0");
258+
set_option(&options, "offset_x", std::to_string(settings.x));
259+
set_option(&options, "offset_y", std::to_string(settings.y));
242260
}
243261
for (const auto& option : settings.options) {
244262
if (option.first == "input_format_name") {

tests/CameraCaptureReader.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ using namespace openshot;
1919

2020
TEST_CASE("Camera capture settings validation", "[libopenshot][cameracapturereader]")
2121
{
22-
#if defined(__linux__)
2322
CameraCaptureSettings settings;
23+
#if defined(__linux__)
2424
settings.backend = CAMERA_CAPTURE_V4L2;
2525
settings.device = "/dev/video0";
26+
#elif defined(_WIN32)
27+
settings.backend = CAMERA_CAPTURE_WINDOWS_DSHOW;
28+
settings.device = "Integrated Camera";
29+
#endif
30+
#if defined(__linux__) || defined(_WIN32)
2631
settings.width = 640;
2732
settings.height = 480;
2833
settings.fps = Fraction(30, 1);
@@ -42,10 +47,15 @@ TEST_CASE("Camera capture settings validation", "[libopenshot][cameracaptureread
4247

4348
TEST_CASE("Camera capture reader reports configured video info", "[libopenshot][cameracapturereader]")
4449
{
45-
#if defined(__linux__)
4650
CameraCaptureSettings settings;
51+
#if defined(__linux__)
4752
settings.backend = CAMERA_CAPTURE_V4L2;
4853
settings.device = "/dev/video9";
54+
#elif defined(_WIN32)
55+
settings.backend = CAMERA_CAPTURE_WINDOWS_DSHOW;
56+
settings.device = "Integrated Camera";
57+
#endif
58+
#if defined(__linux__) || defined(_WIN32)
4959
settings.width = 1280;
5060
settings.height = 720;
5161
settings.fps = Fraction(24, 1);
@@ -67,3 +77,11 @@ TEST_CASE("Camera capture reader reports configured video info", "[libopenshot][
6777
CHECK_FALSE(CameraCaptureReader::IsBackendSupported(CAMERA_CAPTURE_V4L2));
6878
#endif
6979
}
80+
81+
TEST_CASE("Camera capture default backend follows platform", "[libopenshot][cameracapturereader]")
82+
{
83+
#if defined(_WIN32)
84+
CHECK(CameraCaptureReader::IsBackendSupported(CAMERA_CAPTURE_WINDOWS_DSHOW));
85+
CHECK(CameraCaptureReader::DefaultBackend() == CAMERA_CAPTURE_WINDOWS_DSHOW);
86+
#endif
87+
}

tests/ScreenCaptureReader.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@ using namespace openshot;
2222

2323
TEST_CASE("Screen capture settings validation", "[libopenshot][screencapturereader]")
2424
{
25-
#if defined(__linux__)
2625
ScreenCaptureSettings settings;
26+
#if defined(__linux__)
2727
settings.backend = SCREEN_CAPTURE_X11;
2828
settings.display = ":0.0";
29+
#elif defined(_WIN32)
30+
settings.backend = SCREEN_CAPTURE_WINDOWS_GDI;
31+
settings.display = "desktop";
32+
#endif
33+
#if defined(__linux__) || defined(_WIN32)
2934
settings.width = 640;
3035
settings.height = 360;
3136
settings.fps = Fraction(30, 1);
@@ -45,10 +50,15 @@ TEST_CASE("Screen capture settings validation", "[libopenshot][screencaptureread
4550

4651
TEST_CASE("Screen capture reader reports configured video info", "[libopenshot][screencapturereader]")
4752
{
48-
#if defined(__linux__)
4953
ScreenCaptureSettings settings;
54+
#if defined(__linux__)
5055
settings.backend = SCREEN_CAPTURE_X11;
5156
settings.display = ":99.0";
57+
#elif defined(_WIN32)
58+
settings.backend = SCREEN_CAPTURE_WINDOWS_GDI;
59+
settings.display = "desktop";
60+
#endif
61+
#if defined(__linux__) || defined(_WIN32)
5262
settings.x = 10;
5363
settings.y = 20;
5464
settings.width = 800;
@@ -74,7 +84,9 @@ TEST_CASE("Screen capture reader reports configured video info", "[libopenshot][
7484
CHECK(json["y"].asInt() == 20);
7585
CHECK(json["include_cursor"].asBool() == false);
7686
CHECK(json["show_region"].asBool() == true);
87+
#if defined(__linux__)
7788
CHECK(json["options"]["window_id"].asString() == "12345");
89+
#endif
7890
#else
7991
CHECK_FALSE(ScreenCaptureReader::IsBackendSupported(SCREEN_CAPTURE_X11));
8092
#endif
@@ -96,13 +108,25 @@ TEST_CASE("Screen capture backend support follows platform build features", "[li
96108
} else {
97109
CHECK_THROWS_AS([&wayland_settings]() { ScreenCaptureReader reader(wayland_settings); }(), InvalidOptions);
98110
}
111+
#else
112+
#if defined(_WIN32)
113+
CHECK(ScreenCaptureReader::IsBackendSupported(SCREEN_CAPTURE_AUTO));
114+
CHECK(ScreenCaptureReader::IsBackendSupported(SCREEN_CAPTURE_WINDOWS_GDI));
99115
#else
100116
CHECK_FALSE(ScreenCaptureReader::IsBackendSupported(SCREEN_CAPTURE_AUTO));
117+
#endif
101118
CHECK_FALSE(ScreenCaptureReader::IsBackendSupported(SCREEN_CAPTURE_X11));
102119
CHECK_FALSE(ScreenCaptureReader::IsBackendSupported(SCREEN_CAPTURE_WAYLAND));
103120
#endif
104121
}
105122

123+
TEST_CASE("Screen capture default backend follows platform", "[libopenshot][screencapturereader]")
124+
{
125+
#if defined(_WIN32)
126+
CHECK(ScreenCaptureReader::DefaultBackend() == SCREEN_CAPTURE_WINDOWS_GDI);
127+
#endif
128+
}
129+
106130
TEST_CASE("Screen capture auto backend prefers Wayland only when supported", "[libopenshot][screencapturereader]")
107131
{
108132
#if defined(__linux__)

0 commit comments

Comments
 (0)