OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
CameraCaptureReader.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2026 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "CameraCaptureReader.h"
14
15#if defined(__linux__)
16#include "CameraCaptureV4L2.h"
17#include <fcntl.h>
18#include <unistd.h>
19#include <sys/ioctl.h>
20#endif
21
22#include <cstdlib>
23#include <string>
24
25#if defined(_WIN32)
26 #define WIN32_LEAN_AND_MEAN
27 #include <windows.h>
28 #include <dshow.h>
29 #include <oleauto.h>
30#endif
31
32extern "C" {
33 #include <libavdevice/avdevice.h>
34}
35
36#include "Exceptions.h"
37
38using namespace openshot;
39
40#if defined(_WIN32)
41namespace
42{
43 std::string WideToUtf8(const wchar_t* value)
44 {
45 if (!value || !*value) {
46 return "";
47 }
48 const int length = WideCharToMultiByte(CP_UTF8, 0, value, -1, nullptr, 0, nullptr, nullptr);
49 if (length <= 1) {
50 return "";
51 }
52 std::string converted(static_cast<size_t>(length - 1), '\0');
53 WideCharToMultiByte(CP_UTF8, 0, value, -1, &converted[0], length, nullptr, nullptr);
54 return converted;
55 }
56
57 bool ContainsDeviceName(const AudioDeviceList& devices, const std::string& name)
58 {
59 for (const auto& device : devices) {
60 if (device.second == name) {
61 return true;
62 }
63 }
64 return false;
65 }
66
67 AudioDeviceList GetWindowsDirectShowVideoDevices()
68 {
69 AudioDeviceList devices;
70 const HRESULT init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
71 const bool should_uninitialize = SUCCEEDED(init_result);
72 if (FAILED(init_result) && init_result != RPC_E_CHANGED_MODE) {
73 return devices;
74 }
75
76 ICreateDevEnum* device_enumerator = nullptr;
77 HRESULT result = CoCreateInstance(
78 CLSID_SystemDeviceEnum,
79 nullptr,
80 CLSCTX_INPROC_SERVER,
81 IID_ICreateDevEnum,
82 reinterpret_cast<void**>(&device_enumerator)
83 );
84 if (SUCCEEDED(result) && device_enumerator) {
85 IEnumMoniker* moniker_enumerator = nullptr;
86 result = device_enumerator->CreateClassEnumerator(
87 CLSID_VideoInputDeviceCategory,
88 &moniker_enumerator,
89 0
90 );
91 if (result == S_OK && moniker_enumerator) {
92 IMoniker* moniker = nullptr;
93 while (moniker_enumerator->Next(1, &moniker, nullptr) == S_OK) {
94 IPropertyBag* property_bag = nullptr;
95 result = moniker->BindToStorage(
96 nullptr,
97 nullptr,
98 IID_IPropertyBag,
99 reinterpret_cast<void**>(&property_bag)
100 );
101 if (SUCCEEDED(result) && property_bag) {
102 VARIANT friendly_name;
103 VariantInit(&friendly_name);
104 result = property_bag->Read(L"FriendlyName", &friendly_name, nullptr);
105 if (SUCCEEDED(result) && friendly_name.vt == VT_BSTR) {
106 const std::string name = WideToUtf8(friendly_name.bstrVal);
107 if (!name.empty() && !ContainsDeviceName(devices, name)) {
108 devices.emplace_back(name, name);
109 }
110 }
111 VariantClear(&friendly_name);
112 property_bag->Release();
113 }
114 moniker->Release();
115 moniker = nullptr;
116 }
117 moniker_enumerator->Release();
118 }
119 device_enumerator->Release();
120 }
121 if (should_uninitialize) {
122 CoUninitialize();
123 }
124 return devices;
125 }
126}
127#endif
128
130 : settings(new_settings)
131 , reader(nullptr)
132{
133 if (settings.backend == CAMERA_CAPTURE_AUTO) {
134 settings.backend = DefaultBackend();
135 }
136 ValidateSettings();
137 RebuildReader();
138}
139
144
146{
147#if defined(__linux__)
148 return backend == CAMERA_CAPTURE_V4L2 || backend == CAMERA_CAPTURE_AUTO;
149#elif defined(_WIN32)
150 return backend == CAMERA_CAPTURE_WINDOWS_DSHOW || backend == CAMERA_CAPTURE_AUTO;
151#elif defined(__APPLE__)
152 return backend == CAMERA_CAPTURE_MAC_AVFOUNDATION || backend == CAMERA_CAPTURE_AUTO;
153#else
154 (void) backend;
155 return false;
156#endif
157}
158
160{
161#if defined(__linux__)
162 return CAMERA_CAPTURE_V4L2;
163#elif defined(_WIN32)
165#elif defined(__APPLE__)
167#else
168 return CAMERA_CAPTURE_AUTO;
169#endif
170}
171
173{
174 if (backend == CAMERA_CAPTURE_AUTO) {
175 backend = DefaultBackend();
176 }
177
178 AudioDeviceList devices;
179 const char* input_format_name = nullptr;
180#if defined(__linux__)
181 if (backend == CAMERA_CAPTURE_V4L2) {
182 input_format_name = "v4l2";
183 }
184#elif defined(_WIN32)
185 if (backend == CAMERA_CAPTURE_WINDOWS_DSHOW) {
186 return GetWindowsDirectShowVideoDevices();
187 }
188#elif defined(__APPLE__)
189 if (backend == CAMERA_CAPTURE_MAC_AVFOUNDATION) {
190 input_format_name = "avfoundation";
191 }
192#endif
193 if (!input_format_name) {
194 return devices;
195 }
196
197 avdevice_register_all();
198 AVInputFormat* input_format = const_cast<AVInputFormat*>(av_find_input_format(input_format_name));
199 if (!input_format) {
200 return devices;
201 }
202
203 AVDeviceInfoList* device_list = nullptr;
204 const int result = avdevice_list_input_sources(input_format, nullptr, nullptr, &device_list);
205 if (result >= 0 && device_list) {
206 for (int index = 0; index < device_list->nb_devices; ++index) {
207 const AVDeviceInfo* device = device_list->devices[index];
208 if (!device || !device->device_name) {
209 continue;
210 }
211 const std::string name = device->device_name;
212 const std::string label = device->device_description
213 ? device->device_description
214 : device->device_name;
215 #if defined(__APPLE__)
216 if (backend == CAMERA_CAPTURE_MAC_AVFOUNDATION && label.find("Capture screen") != std::string::npos) {
217 continue;
218 }
219 #endif
220 devices.emplace_back(label, name);
221 }
222 }
223 avdevice_free_list_devices(&device_list);
224 return devices;
225}
226
227std::vector<CameraCaptureMode> CameraCaptureReader::GetDeviceModes(
228 const std::string& device, CameraCaptureBackend backend)
229{
230 if (backend == CAMERA_CAPTURE_AUTO) backend = DefaultBackend();
231#if defined(__linux__)
232 if (backend == CAMERA_CAPTURE_V4L2) {
233 const int fd = open(device.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);
234 if (fd < 0) throw InvalidFile("Unable to open camera for mode discovery.", device);
235 struct DeviceHandle {
236 int fd;
237 ~DeviceHandle() { close(fd); }
238 } handle{fd};
239 return detail::EnumerateCameraModes([fd](unsigned long request, void* value) {
240 return ioctl(fd, request, value);
241 });
242 }
243#else
244 (void) device;
245#endif
246 return {};
247}
248
249void CameraCaptureReader::ValidateSettings() const
250{
251 if (!IsBackendSupported(settings.backend)) {
252 throw InvalidOptions("Camera capture backend is not supported on this OS.");
253 }
254 if (settings.backend != CAMERA_CAPTURE_V4L2
257 throw InvalidOptions("Camera capture backend is not implemented in this build.");
258 }
259 if (settings.device.empty()) {
260 throw InvalidOptions("Camera capture requires a device path.");
261 }
262 if (settings.width <= 0 || settings.height <= 0) {
263 throw InvalidOptions("Camera capture requires a positive width and height.");
264 }
265 if (settings.fps.num <= 0 || settings.fps.den <= 0) {
266 throw InvalidOptions("Camera capture requires a positive frame rate.");
267 }
268}
269
270ScreenCaptureSettings CameraCaptureReader::ToDeviceSettings() const
271{
272 ScreenCaptureSettings converted;
273 converted.display = settings.device;
274 converted.width = settings.width;
275 converted.height = settings.height;
276 converted.fps = settings.fps;
277 converted.options = settings.options;
278 if (settings.backend == CAMERA_CAPTURE_WINDOWS_DSHOW) {
280 converted.display = "video=" + settings.device;
281 converted.options["input_format_name"] = "dshow";
282 } else if (settings.backend == CAMERA_CAPTURE_MAC_AVFOUNDATION) {
284 converted.display = settings.device.find(':') == std::string::npos
285 ? settings.device + ":none"
286 : settings.device;
287 converted.options["input_format_name"] = "avfoundation";
288 } else {
289 converted.backend = SCREEN_CAPTURE_X11;
290 converted.options["input_format_name"] = "v4l2";
291 }
292 return converted;
293}
294
295void CameraCaptureReader::RebuildReader()
296{
297 reader = std::make_unique<ScreenCaptureReader>(ToDeviceSettings());
298 info = reader->info;
299 info.metadata["capture_type"] = "camera";
300}
301
303{
304 reader->Open();
305 info = reader->info;
306 info.metadata["capture_type"] = "camera";
307}
308
310{
311 if (reader) {
312 reader->Close();
313 }
314}
315
317{
318 if (reader) {
319 reader->RequestClose();
320 }
321}
322
324{
325 return reader ? reader->GetStats() : CaptureReaderStats();
326}
327
328std::shared_ptr<Frame> CameraCaptureReader::GetFrame(int64_t number)
329{
330 return reader->GetFrame(number);
331}
332
333std::string CameraCaptureReader::Json() const
334{
335 return JsonValue().toStyledString();
336}
337
339{
340 Json::Value root = ReaderBase::JsonValue();
341 root["type"] = "CameraCaptureReader";
342 root["backend"] = settings.backend;
343 root["device"] = settings.device;
344 root["width"] = settings.width;
345 root["height"] = settings.height;
346 root["fps"]["num"] = settings.fps.num;
347 root["fps"]["den"] = settings.fps.den;
348 root["options"] = Json::Value(Json::objectValue);
349 for (const auto& option : settings.options) {
350 root["options"][option.first] = option.second;
351 }
352 return root;
353}
354
355void CameraCaptureReader::SetJson(const std::string value)
356{
357 try {
359 } catch (const std::exception&) {
360 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
361 }
362}
363
364void CameraCaptureReader::SetJsonValue(const Json::Value root)
365{
366 if (!root["backend"].isNull())
367 settings.backend = static_cast<CameraCaptureBackend>(root["backend"].asInt());
368 if (!root["device"].isNull())
369 settings.device = root["device"].asString();
370 if (!root["width"].isNull())
371 settings.width = root["width"].asInt();
372 if (!root["height"].isNull())
373 settings.height = root["height"].asInt();
374 if (!root["fps"].isNull() && root["fps"].isObject()) {
375 if (!root["fps"]["num"].isNull())
376 settings.fps.num = root["fps"]["num"].asInt();
377 if (!root["fps"]["den"].isNull())
378 settings.fps.den = root["fps"]["den"].asInt();
379 }
380 if (!root["options"].isNull() && root["options"].isObject()) {
381 settings.options.clear();
382 for (const auto& key : root["options"].getMemberNames()) {
383 settings.options[key] = root["options"][key].asString();
384 }
385 }
386 if (settings.backend == CAMERA_CAPTURE_AUTO) {
387 settings.backend = DefaultBackend();
388 }
389 ValidateSettings();
390 RebuildReader();
391}
std::vector< std::pair< std::string, std::string > > AudioDeviceList
Header file for live camera capture readers.
Header file for all Exception classes.
void Open() override
Open the reader (and start consuming resources, such as images or video files)
Json::Value JsonValue() const override
Generate Json::Value for this object.
void Close() override
Close the reader (and any resources it was consuming)
static bool IsBackendSupported(CameraCaptureBackend backend)
static AudioDeviceList GetDeviceNames(CameraCaptureBackend backend=CAMERA_CAPTURE_AUTO)
void RequestClose()
Request cancellation without releasing camera resources on the caller's thread.
CameraCaptureReader(const CameraCaptureSettings &settings)
std::string Json() const override
Generate JSON string of this object.
openshot::CaptureReaderStats GetStats() const
std::shared_ptr< openshot::Frame > GetFrame(int64_t number) override
static std::vector< CameraCaptureMode > GetDeviceModes(const std::string &device, CameraCaptureBackend backend=CAMERA_CAPTURE_AUTO)
static CameraCaptureBackend DefaultBackend()
void SetJson(const std::string value) override
Load JSON string into this object.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
int num
Numerator for the fraction.
Definition Fraction.h:32
int den
Denominator for the fraction.
Definition Fraction.h:33
Exception for files that can not be found or opened.
Definition Exceptions.h:194
Exception for invalid JSON.
Definition Exceptions.h:224
Exception when invalid encoding options are used.
Definition Exceptions.h:239
openshot::ReaderInfo info
Information about the current media file.
Definition ReaderBase.h:91
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
This namespace is the default namespace for all code in the openshot library.
@ SCREEN_CAPTURE_MAC_AVFOUNDATION
@ CAMERA_CAPTURE_MAC_AVFOUNDATION
std::vector< std::pair< std::string, std::string > > AudioDeviceList
const Json::Value stringToJson(const std::string value)
Definition Json.cpp:16
std::map< std::string, std::string > options
std::map< std::string, std::string > metadata
An optional map/dictionary of metadata for this reader.
Definition ReaderBase.h:65
std::map< std::string, std::string > options