utilwebcam.js 12 KB

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