OpenShot Library | libopenshot  1.0.0
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 #include <cstdlib>
16 #include <string>
17 
18 #if defined(_WIN32)
19  #define WIN32_LEAN_AND_MEAN
20  #include <windows.h>
21  #include <dshow.h>
22  #include <oleauto.h>
23 #endif
24 
25 extern "C" {
26  #include <libavdevice/avdevice.h>
27 }
28 
29 #include "Exceptions.h"
30 
31 using namespace openshot;
32 
33 #if defined(_WIN32)
34 namespace
35 {
36  std::string WideToUtf8(const wchar_t* value)
37  {
38  if (!value || !*value) {
39  return "";
40  }
41  const int length = WideCharToMultiByte(CP_UTF8, 0, value, -1, nullptr, 0, nullptr, nullptr);
42  if (length <= 1) {
43  return "";
44  }
45  std::string converted(static_cast<size_t>(length - 1), '\0');
46  WideCharToMultiByte(CP_UTF8, 0, value, -1, &converted[0], length, nullptr, nullptr);
47  return converted;
48  }
49 
50  bool ContainsDeviceName(const AudioDeviceList& devices, const std::string& name)
51  {
52  for (const auto& device : devices) {
53  if (device.second == name) {
54  return true;
55  }
56  }
57  return false;
58  }
59 
60  AudioDeviceList GetWindowsDirectShowVideoDevices()
61  {
62  AudioDeviceList devices;
63  const HRESULT init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
64  const bool should_uninitialize = SUCCEEDED(init_result);
65  if (FAILED(init_result) && init_result != RPC_E_CHANGED_MODE) {
66  return devices;
67  }
68 
69  ICreateDevEnum* device_enumerator = nullptr;
70  HRESULT result = CoCreateInstance(
71  CLSID_SystemDeviceEnum,
72  nullptr,
73  CLSCTX_INPROC_SERVER,
74  IID_ICreateDevEnum,
75  reinterpret_cast<void**>(&device_enumerator)
76  );
77  if (SUCCEEDED(result) && device_enumerator) {
78  IEnumMoniker* moniker_enumerator = nullptr;
79  result = device_enumerator->CreateClassEnumerator(
80  CLSID_VideoInputDeviceCategory,
81  &moniker_enumerator,
82  0
83  );
84  if (result == S_OK && moniker_enumerator) {
85  IMoniker* moniker = nullptr;
86  while (moniker_enumerator->Next(1, &moniker, nullptr) == S_OK) {
87  IPropertyBag* property_bag = nullptr;
88  result = moniker->BindToStorage(
89  nullptr,
90  nullptr,
91  IID_IPropertyBag,
92  reinterpret_cast<void**>(&property_bag)
93  );
94  if (SUCCEEDED(result) && property_bag) {
95  VARIANT friendly_name;
96  VariantInit(&friendly_name);
97  result = property_bag->Read(L"FriendlyName", &friendly_name, nullptr);
98  if (SUCCEEDED(result) && friendly_name.vt == VT_BSTR) {
99  const std::string name = WideToUtf8(friendly_name.bstrVal);
100  if (!name.empty() && !ContainsDeviceName(devices, name)) {
101  devices.emplace_back(name, name);
102  }
103  }
104  VariantClear(&friendly_name);
105  property_bag->Release();
106  }
107  moniker->Release();
108  moniker = nullptr;
109  }
110  moniker_enumerator->Release();
111  }
112  device_enumerator->Release();
113  }
114  if (should_uninitialize) {
115  CoUninitialize();
116  }
117  return devices;
118  }
119 }
120 #endif
121 
123  : settings(new_settings)
124  , reader(nullptr)
125 {
126  if (settings.backend == CAMERA_CAPTURE_AUTO) {
127  settings.backend = DefaultBackend();
128  }
129  ValidateSettings();
130  RebuildReader();
131 }
132 
134 {
135  Close();
136 }
137 
139 {
140 #if defined(__linux__)
141  return backend == CAMERA_CAPTURE_V4L2 || backend == CAMERA_CAPTURE_AUTO;
142 #elif defined(_WIN32)
143  return backend == CAMERA_CAPTURE_WINDOWS_DSHOW || backend == CAMERA_CAPTURE_AUTO;
144 #elif defined(__APPLE__)
145  return backend == CAMERA_CAPTURE_MAC_AVFOUNDATION || backend == CAMERA_CAPTURE_AUTO;
146 #else
147  (void) backend;
148  return false;
149 #endif
150 }
151 
153 {
154 #if defined(__linux__)
155  return CAMERA_CAPTURE_V4L2;
156 #elif defined(_WIN32)
158 #elif defined(__APPLE__)
160 #else
161  return CAMERA_CAPTURE_AUTO;
162 #endif
163 }
164 
166 {
167  if (backend == CAMERA_CAPTURE_AUTO) {
168  backend = DefaultBackend();
169  }
170 
171  AudioDeviceList devices;
172  const char* input_format_name = nullptr;
173 #if defined(__linux__)
174  if (backend == CAMERA_CAPTURE_V4L2) {
175  input_format_name = "v4l2";
176  }
177 #elif defined(_WIN32)
178  if (backend == CAMERA_CAPTURE_WINDOWS_DSHOW) {
179  return GetWindowsDirectShowVideoDevices();
180  }
181 #elif defined(__APPLE__)
182  if (backend == CAMERA_CAPTURE_MAC_AVFOUNDATION) {
183  input_format_name = "avfoundation";
184  }
185 #endif
186  if (!input_format_name) {
187  return devices;
188  }
189 
190  avdevice_register_all();
191  AVInputFormat* input_format = const_cast<AVInputFormat*>(av_find_input_format(input_format_name));
192  if (!input_format) {
193  return devices;
194  }
195 
196  AVDeviceInfoList* device_list = nullptr;
197  const int result = avdevice_list_input_sources(input_format, nullptr, nullptr, &device_list);
198  if (result >= 0 && device_list) {
199  for (int index = 0; index < device_list->nb_devices; ++index) {
200  const AVDeviceInfo* device = device_list->devices[index];
201  if (!device || !device->device_name) {
202  continue;
203  }
204  const std::string name = device->device_name;
205  const std::string label = device->device_description
206  ? device->device_description
207  : device->device_name;
208  #if defined(__APPLE__)
209  if (backend == CAMERA_CAPTURE_MAC_AVFOUNDATION && label.find("Capture screen") != std::string::npos) {
210  continue;
211  }
212  #endif
213  devices.emplace_back(label, name);
214  }
215  }
216  avdevice_free_list_devices(&device_list);
217  return devices;
218 }
219 
220 void CameraCaptureReader::ValidateSettings() const
221 {
222  if (!IsBackendSupported(settings.backend)) {
223  throw InvalidOptions("Camera capture backend is not supported on this OS.");
224  }
225  if (settings.backend != CAMERA_CAPTURE_V4L2
227  && settings.backend != CAMERA_CAPTURE_MAC_AVFOUNDATION) {
228  throw InvalidOptions("Camera capture backend is not implemented in this build.");
229  }
230  if (settings.device.empty()) {
231  throw InvalidOptions("Camera capture requires a device path.");
232  }
233  if (settings.width <= 0 || settings.height <= 0) {
234  throw InvalidOptions("Camera capture requires a positive width and height.");
235  }
236  if (settings.fps.num <= 0 || settings.fps.den <= 0) {
237  throw InvalidOptions("Camera capture requires a positive frame rate.");
238  }
239 }
240 
241 ScreenCaptureSettings CameraCaptureReader::ToDeviceSettings() const
242 {
243  ScreenCaptureSettings converted;
244  converted.display = settings.device;
245  converted.width = settings.width;
246  converted.height = settings.height;
247  converted.fps = settings.fps;
248  converted.options = settings.options;
249  if (settings.backend == CAMERA_CAPTURE_WINDOWS_DSHOW) {
251  converted.display = "video=" + settings.device;
252  converted.options["input_format_name"] = "dshow";
253  } else if (settings.backend == CAMERA_CAPTURE_MAC_AVFOUNDATION) {
255  converted.display = settings.device.find(':') == std::string::npos
256  ? settings.device + ":none"
257  : settings.device;
258  converted.options["input_format_name"] = "avfoundation";
259  } else {
260  converted.backend = SCREEN_CAPTURE_X11;
261  converted.options["input_format_name"] = "v4l2";
262  }
263  return converted;
264 }
265 
266 void CameraCaptureReader::RebuildReader()
267 {
268  reader = std::make_unique<ScreenCaptureReader>(ToDeviceSettings());
269  info = reader->info;
270  info.metadata["capture_type"] = "camera";
271 }
272 
274 {
275  reader->Open();
276  info = reader->info;
277  info.metadata["capture_type"] = "camera";
278 }
279 
281 {
282  if (reader) {
283  reader->Close();
284  }
285 }
286 
288 {
289  return reader ? reader->GetStats() : CaptureReaderStats();
290 }
291 
292 std::shared_ptr<Frame> CameraCaptureReader::GetFrame(int64_t number)
293 {
294  return reader->GetFrame(number);
295 }
296 
297 std::string CameraCaptureReader::Json() const
298 {
299  return JsonValue().toStyledString();
300 }
301 
303 {
304  Json::Value root = ReaderBase::JsonValue();
305  root["type"] = "CameraCaptureReader";
306  root["backend"] = settings.backend;
307  root["device"] = settings.device;
308  root["width"] = settings.width;
309  root["height"] = settings.height;
310  root["fps"]["num"] = settings.fps.num;
311  root["fps"]["den"] = settings.fps.den;
312  root["options"] = Json::Value(Json::objectValue);
313  for (const auto& option : settings.options) {
314  root["options"][option.first] = option.second;
315  }
316  return root;
317 }
318 
319 void CameraCaptureReader::SetJson(const std::string value)
320 {
321  try {
323  } catch (const std::exception&) {
324  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
325  }
326 }
327 
328 void CameraCaptureReader::SetJsonValue(const Json::Value root)
329 {
330  if (!root["backend"].isNull())
331  settings.backend = static_cast<CameraCaptureBackend>(root["backend"].asInt());
332  if (!root["device"].isNull())
333  settings.device = root["device"].asString();
334  if (!root["width"].isNull())
335  settings.width = root["width"].asInt();
336  if (!root["height"].isNull())
337  settings.height = root["height"].asInt();
338  if (!root["fps"].isNull() && root["fps"].isObject()) {
339  if (!root["fps"]["num"].isNull())
340  settings.fps.num = root["fps"]["num"].asInt();
341  if (!root["fps"]["den"].isNull())
342  settings.fps.den = root["fps"]["den"].asInt();
343  }
344  if (!root["options"].isNull() && root["options"].isObject()) {
345  settings.options.clear();
346  for (const auto& key : root["options"].getMemberNames()) {
347  settings.options[key] = root["options"][key].asString();
348  }
349  }
350  if (settings.backend == CAMERA_CAPTURE_AUTO) {
351  settings.backend = DefaultBackend();
352  }
353  ValidateSettings();
354  RebuildReader();
355 }
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)
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 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 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.
Definition: ReaderBase.cpp:110
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
@ SCREEN_CAPTURE_MAC_AVFOUNDATION
@ SCREEN_CAPTURE_WINDOWS_GDI
@ CAMERA_CAPTURE_WINDOWS_DSHOW
@ CAMERA_CAPTURE_MAC_AVFOUNDATION
std::vector< std::pair< std::string, std::string > > AudioDeviceList
Definition: AudioDevices.h:42
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