utilwebcam.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /*
  2. Copyright (C) 2015 Borsato Ivano
  3. The JavaScript code in this page is free software: you can
  4. redistribute it and/or modify it under the terms of the GNU
  5. General Public License (GNU GPL) as published by the Free Software
  6. Foundation, either version 3 of the License, or (at your option)
  7. any later version. The code is distributed WITHOUT ANY WARRANTY;
  8. without even the implied warranty of MERCHANTABILITY or FITNESS
  9. FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
  10. */
  11. 'use strict';
  12. import GObject from 'gi://GObject';
  13. import GLib from 'gi://GLib';
  14. import Gst from 'gi://Gst?version=1.0';
  15. import * as Lib from './convenience.js';
  16. export const HelperWebcam = GObject.registerClass({
  17. GTypeName: 'EasyScreenCast_HelperWebcam',
  18. }, class HelperWebcam extends GObject.Object {
  19. /**
  20. * Create a device monitor inputvideo
  21. *
  22. * @param {string} unspecifiedWebcamText localized text for "Unspecified Webcam"
  23. */
  24. constructor(unspecifiedWebcamText) {
  25. super();
  26. this._unspecified_webcam_text = unspecifiedWebcamText;
  27. Lib.TalkativeLog('-@-init webcam');
  28. var [result, _] = Gst.init_check(null);
  29. Lib.TalkativeLog(`-@-gstreamer init result: ${result}`);
  30. if (!result) {
  31. Lib.TalkativeLog('-@-gstreamer init failed');
  32. throw new Error('GStreamer init failed');
  33. }
  34. // get gstreamer lib version
  35. var [M, m, micro, nano] = Gst.version();
  36. Lib.TalkativeLog(
  37. `-@-gstreamer version: ${M}.${m}.${micro}.${nano}`
  38. );
  39. if (M === 1 && m >= 8) {
  40. // gstreamer version equal or higher 1.8
  41. this.deviceMonitor = new Gst.DeviceMonitor({
  42. show_all: true,
  43. });
  44. } else {
  45. // gstreamer version lower 1.8
  46. this.deviceMonitor = new Gst.DeviceMonitor();
  47. }
  48. // get gstreamer plugin avaiable
  49. let registry = new Gst.Registry();
  50. let listPI = registry.get_plugin_list();
  51. Lib.TalkativeLog(`-@-plugin list: ${listPI.length}`);
  52. for (var ind in listPI) {
  53. Lib.TalkativeLog(
  54. `-@-plugin name: ${
  55. listPI[ind].get_name()
  56. } Pfilename: ${
  57. listPI[ind].get_filename()
  58. } Pdesc: ${
  59. listPI[ind].get_description()
  60. } Pversion: ${
  61. listPI[ind].get_version()
  62. } Pload: ${
  63. listPI[ind].is_loaded()}`
  64. );
  65. }
  66. // create device monitor
  67. if (this.deviceMonitor !== null && this.deviceMonitor !== undefined) {
  68. Lib.TalkativeLog('-@-device monitor created');
  69. this.dmBus = this.deviceMonitor.get_bus();
  70. if (this.dmBus !== null && this.dmBus !== undefined) {
  71. Lib.TalkativeLog('-@-dbus created');
  72. this.dmBus.add_watch(GLib.PRIORITY_DEFAULT, this._getMsg.bind(this));
  73. let caps = Gst.Caps.new_empty_simple('video/x-raw');
  74. this.deviceMonitor.add_filter('Video/Source', caps);
  75. this.startMonitor();
  76. // update device and caps
  77. this.refreshAllInputVideo();
  78. } else {
  79. Lib.TalkativeLog('-@-ERROR dbus creation');
  80. }
  81. } else {
  82. Lib.TalkativeLog('-@-ERROR device monitor creation');
  83. }
  84. }
  85. /**
  86. * Callback for the DeviceMonitor watcher
  87. *
  88. * @param {Gst.Bus} bus the DeviceMonitor Bus of gstreamer
  89. * @param {Gst.Message} message the message
  90. * @returns {boolean}
  91. */
  92. _getMsg(bus, message) {
  93. Lib.TalkativeLog('-@-event getmsg');
  94. switch (message.type) {
  95. case Gst.MessageType.DEVICE_ADDED:
  96. Lib.TalkativeLog('Device added');
  97. // update device and caps
  98. this.refreshAllInputVideo();
  99. break;
  100. case Gst.MessageType.DEVICE_REMOVED:
  101. Lib.TalkativeLog('Device removed');
  102. // update device and caps
  103. this.refreshAllInputVideo();
  104. break;
  105. default:
  106. Lib.TalkativeLog('Device UNK');
  107. break;
  108. }
  109. return GLib.SOURCE_CONTINUE;
  110. }
  111. /**
  112. * refresh all devices info
  113. */
  114. refreshAllInputVideo() {
  115. Lib.TalkativeLog('-@-refresh all video input');
  116. this._listDevices = this.getDevicesIV();
  117. // compose devices array
  118. this._listCaps = [];
  119. for (var index in this._listDevices) {
  120. this._listCaps[index] = this.getCapsForIV(this._listDevices[index].caps);
  121. Lib.TalkativeLog(`-@-webcam /dev/video${index} name: ${this._listDevices[index].display_name}`);
  122. Lib.TalkativeLog(`-@-caps available: ${this._listCaps[index].length}`);
  123. Lib.TalkativeLog(`-@-ListCaps[${index}]: ${this._listCaps[index]}`);
  124. }
  125. }
  126. /**
  127. * get caps from device.
  128. * A single capability might look like: <code>video/x-raw, format=(string)YUY2, width=(int)640, height=(int)480, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)30/1</code>
  129. * This encodes a single capability (fixed), but there might also be capabilities which represent options, e.g.
  130. * <code>video/x-raw, format=(string)YUY2, width=(int)640, height=(int)480, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction) { 30/1, 25/1, 20/1 }</code>.
  131. * <br>
  132. * The code here will take always the first option and unroll the options for framerate.
  133. *
  134. * @param {Gst.Caps} tmpCaps capabilities of a device
  135. * @returns {string[]}
  136. */
  137. getCapsForIV(tmpCaps) {
  138. Lib.TalkativeLog('-@-get all caps from a input video');
  139. Lib.TalkativeLog(`-@-caps available before filtering for video/x-raw: ${tmpCaps.get_size()}`);
  140. let cleanCaps = [];
  141. for (let i = 0; i < tmpCaps.get_size(); i++) {
  142. let capsStructure = tmpCaps.get_structure(i);
  143. // only consider "video/x-raw"
  144. if (capsStructure.get_name() === 'video/x-raw') {
  145. Lib.TalkativeLog(`-@-cap : ${i} : original : ${capsStructure.to_string()}`);
  146. let tmpStr = 'video/x-raw';
  147. let result, number, fraction;
  148. result = capsStructure.get_string('format');
  149. if (result !== null)
  150. tmpStr += `, format=(string)${result}`;
  151. [result, number] = capsStructure.get_int('width');
  152. if (result === true)
  153. tmpStr += `, width=(int)${number}`;
  154. [result, number] = capsStructure.get_int('height');
  155. if (result === true)
  156. tmpStr += `, height=(int)${number}`;
  157. [result, number, fraction] = capsStructure.get_fraction('pixel-aspect-ratio');
  158. if (result === true)
  159. tmpStr += `, pixel-aspect-ratio=(fraction)${number}/${fraction}`;
  160. if (capsStructure.has_field('framerate')) {
  161. [result, number, fraction] = capsStructure.get_fraction('framerate');
  162. if (result === true) {
  163. // a single framerate
  164. this._addAndLogCapability(cleanCaps, i, `${tmpStr}, framerate=(fraction)${number}/${fraction}`);
  165. } else {
  166. // multiple framerates
  167. // unfortunately GstValueList is not supported in this gjs-binding
  168. // "Error: Don't know how to convert GType GstValueList to JavaScript object"
  169. // -> capsStructure.get_value('framerate') <- won't work
  170. // -> capsStructure.get_list('framerate') <- only returns the numerator of the fraction
  171. //
  172. // therefore manually parsing the framerate values from the string representation
  173. let framerates = capsStructure.to_string();
  174. framerates = framerates.substring(framerates.indexOf('framerate=(fraction){') + 21);
  175. framerates = framerates.substring(0, framerates.indexOf('}'));
  176. framerates.split(',').forEach(element => {
  177. let [numerator, denominator] = element.split('/', 2);
  178. this._addAndLogCapability(cleanCaps, i, `${tmpStr}, framerate=(fraction)${numerator.trim()}/${denominator.trim()}`);
  179. });
  180. }
  181. } else {
  182. // no framerate at all
  183. this._addAndLogCapability(cleanCaps, i, tmpStr);
  184. }
  185. } else {
  186. Lib.TalkativeLog(`-@-cap : ${i} : skipped : ${capsStructure.to_string()}`);
  187. }
  188. }
  189. return cleanCaps;
  190. }
  191. /**
  192. * Adds the capability str to the array caps, if it is not already there. Avoids duplicates.
  193. *
  194. * @param {Array} caps the list of capabilities
  195. * @param {int} originalIndex index of the original capabilities list from the device
  196. * @param {string} str the capability string to add
  197. */
  198. _addAndLogCapability(caps, originalIndex, str) {
  199. if (caps.indexOf(str) === -1) {
  200. caps.push(str);
  201. Lib.TalkativeLog(`-@-cap : ${originalIndex} : added cap : ${str}`);
  202. } else {
  203. Lib.TalkativeLog(`-@-cap : ${originalIndex} : ignore duplicated cap : ${str}`);
  204. }
  205. }
  206. /**
  207. * get devices IV
  208. *
  209. * @returns {Gst.Device[]}
  210. */
  211. getDevicesIV() {
  212. Lib.TalkativeLog('-@-get devices');
  213. var list = this.deviceMonitor.get_devices();
  214. Lib.TalkativeLog(`-@-devices number: ${list.length}`);
  215. // Note:
  216. // Although the computer may have just one webcam connected to
  217. // it, more than one GstDevice may be listed and all pointing to
  218. // the same video device (for example /dev/video0. Each
  219. // GstDevice is supposed to be used with a specific source, for
  220. // example, a pipewiresrc or a v4l2src. For now, we are only
  221. // using v4l2src.
  222. // See also: Gst.DeviceMonitor.get_providers: pipewiredeviceprovider,decklinkdeviceprovider,v4l2deviceprovider
  223. // CLI: "/usr/bin/gst-device-monitor-1.0 Video/Source"
  224. //
  225. // So, here we filter the devices, that have a device.path property, which
  226. // means, these are only v4l2 devices
  227. var filtered = list.filter(device => {
  228. let props = device.get_properties();
  229. let hasDevice = props != null && props.get_string('device.path') !== null;
  230. if (props != null)
  231. props.free();
  232. return hasDevice;
  233. });
  234. Lib.TalkativeLog(`-@-devices number after filtering for v4l2: ${filtered.length}`);
  235. return filtered;
  236. }
  237. /**
  238. * get array name devices IV
  239. *
  240. * @returns {Array}
  241. */
  242. getNameDevices() {
  243. Lib.TalkativeLog('-@-get name devices');
  244. let tmpArray = [];
  245. for (var index in this._listDevices) {
  246. var wcName = this._unspecified_webcam_text;
  247. if (this._listDevices[index].display_name !== '')
  248. wcName = this._listDevices[index].display_name;
  249. tmpArray.push(wcName);
  250. }
  251. Lib.TalkativeLog(`-@-list devices name: ${tmpArray}`);
  252. return tmpArray;
  253. }
  254. /**
  255. * get array caps
  256. *
  257. * @param {int} index device
  258. * @returns {string[]}
  259. */
  260. getListCapsDevice(index) {
  261. const tmpArray = this._listCaps[index];
  262. Lib.TalkativeLog(`-@-list caps of device: ${tmpArray}`);
  263. return tmpArray;
  264. }
  265. /**
  266. * start listening
  267. */
  268. startMonitor() {
  269. Lib.TalkativeLog('-@-start video devicemonitor');
  270. this.deviceMonitor.start();
  271. }
  272. /**
  273. * Stop listening
  274. */
  275. stopMonitor() {
  276. Lib.TalkativeLog('-@-stop video devicemonitor');
  277. this.disconnectSourceBus();
  278. this.deviceMonitor.stop();
  279. }
  280. /**
  281. * disconect bus
  282. */
  283. disconnectSourceBus() {
  284. if (this.dmBusId) {
  285. this.dmBus.disconnect(this.dmBusId);
  286. this.dmBusId = 0;
  287. }
  288. }
  289. });