Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

JitsiStreamBlurEffect.js 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import { getLogger } from 'jitsi-meet-logger';
  2. import {
  3. drawBokehEffect,
  4. load
  5. } from '@tensorflow-models/body-pix';
  6. import {
  7. CLEAR_INTERVAL,
  8. INTERVAL_TIMEOUT,
  9. SET_INTERVAL,
  10. timerWorkerScript
  11. } from './TimerWorker';
  12. const logger = getLogger(__filename);
  13. /**
  14. * This promise represents the loading of the BodyPix model that is used
  15. * to extract person segmentation. A multiplier of 0.25 is used to for
  16. * improved performance on a larger range of CPUs.
  17. */
  18. const bpModelPromise = load(0.25);
  19. /**
  20. * Represents a modified MediaStream that adds blur to video background.
  21. * <tt>JitsiStreamBlurEffect</tt> does the processing of the original
  22. * video stream.
  23. */
  24. class JitsiStreamBlurEffect {
  25. /**
  26. *
  27. * Represents a modified video MediaStream track.
  28. *
  29. * @class
  30. * @param {BodyPix} bpModel - BodyPix model
  31. */
  32. constructor(bpModel) {
  33. this._bpModel = bpModel;
  34. this._outputCanvasElement = document.createElement('canvas');
  35. this._maskCanvasElement = document.createElement('canvas');
  36. this._inputVideoElement = document.createElement('video');
  37. this._renderVideo = this._renderVideo.bind(this);
  38. this._renderMask = this._renderMask.bind(this);
  39. this._videoFrameTimerWorker = new Worker(timerWorkerScript);
  40. this._maskFrameTimerWorker = new Worker(timerWorkerScript);
  41. this._onMaskFrameTimer = this._onMaskFrameTimer.bind(this);
  42. this._onVideoFrameTimer = this._onVideoFrameTimer.bind(this);
  43. this._videoFrameTimerWorker.onmessage = this._onVideoFrameTimer;
  44. this._maskFrameTimerWorker.onmessage = this._onMaskFrameTimer;
  45. }
  46. /**
  47. * EventHandler onmessage for the videoFrameTimerWorker WebWorker
  48. *
  49. * @private
  50. * @param {EventHandler} response - onmessage EventHandler parameter
  51. * @returns {void}
  52. */
  53. _onVideoFrameTimer(response) {
  54. switch (response.data.id) {
  55. case INTERVAL_TIMEOUT: {
  56. this._renderVideo();
  57. break;
  58. }
  59. }
  60. }
  61. /**
  62. * EventHandler onmessage for the maskFrameTimerWorker WebWorker
  63. *
  64. * @private
  65. * @param {EventHandler} response - onmessage EventHandler parameter
  66. * @returns {void}
  67. */
  68. _onMaskFrameTimer(response) {
  69. switch (response.data.id) {
  70. case INTERVAL_TIMEOUT: {
  71. this._renderMask();
  72. break;
  73. }
  74. }
  75. }
  76. /**
  77. * Starts loop to capture video frame and render the segmentation mask.
  78. *
  79. * @param {MediaStream} stream - Stream to be used for processing
  80. *
  81. * @returns {void}
  82. */
  83. startEffect(stream) {
  84. this._stream = stream;
  85. const firstVideoTrack = this._stream.getVideoTracks()[0];
  86. const { height, frameRate, width } = firstVideoTrack.getSettings
  87. ? firstVideoTrack.getSettings() : firstVideoTrack.getConstraints();
  88. if (!firstVideoTrack.getSettings && !firstVideoTrack.getConstraints) {
  89. throw new Error('JitsiStreamBlurEffect not supported!');
  90. }
  91. this._frameRate = frameRate;
  92. this._height = height;
  93. this._width = width;
  94. this._outputCanvasElement.width = width;
  95. this._outputCanvasElement.height = height;
  96. this._maskCanvasElement.width = this._width;
  97. this._maskCanvasElement.height = this._height;
  98. this._inputVideoElement.width = width;
  99. this._inputVideoElement.height = height;
  100. this._maskCanvasContext = this._maskCanvasElement.getContext('2d');
  101. this._inputVideoElement.autoplay = true;
  102. this._inputVideoElement.srcObject = this._stream;
  103. this._videoFrameTimerWorker.postMessage({
  104. id: SET_INTERVAL,
  105. timeMs: 1000 / this._frameRate
  106. });
  107. this._maskFrameTimerWorker.postMessage({
  108. id: SET_INTERVAL,
  109. timeMs: 200
  110. });
  111. }
  112. /**
  113. * Stops the capture and render loop.
  114. *
  115. * @returns {void}
  116. */
  117. stopEffect() {
  118. this._videoFrameTimerWorker.postMessage({
  119. id: CLEAR_INTERVAL
  120. });
  121. this._maskFrameTimerWorker.postMessage({
  122. id: CLEAR_INTERVAL
  123. });
  124. }
  125. /**
  126. * Get the modified stream.
  127. *
  128. * @returns {MediaStream}
  129. */
  130. getStreamWithEffect() {
  131. return this._outputCanvasElement.captureStream(this._frameRate);
  132. }
  133. /**
  134. * Loop function to render the video frame input and draw blur effect.
  135. *
  136. * @private
  137. * @returns {void}
  138. */
  139. _renderVideo() {
  140. if (this._bpModel) {
  141. this._maskCanvasContext.drawImage(this._inputVideoElement,
  142. 0,
  143. 0,
  144. this._width,
  145. this._height);
  146. if (this._segmentationData) {
  147. drawBokehEffect(this._outputCanvasElement,
  148. this._inputVideoElement,
  149. this._segmentationData,
  150. 7, // Constant for background blur, integer values between 0-20
  151. 7); // Constant for edge blur, integer values between 0-20
  152. }
  153. } else {
  154. this._outputCanvasElement
  155. .getContext('2d')
  156. .drawImage(this._inputVideoElement,
  157. 0,
  158. 0,
  159. this._width,
  160. this._height);
  161. }
  162. }
  163. /**
  164. * Loop function to render the background mask.
  165. *
  166. * @private
  167. * @returns {void}
  168. */
  169. _renderMask() {
  170. if (this._bpModel) {
  171. this._bpModel.estimatePersonSegmentation(this._maskCanvasElement,
  172. 32, // Chose 32 for better performance
  173. 0.75) // Represents probability that a pixel belongs to a person
  174. .then(value => {
  175. this._segmentationData = value;
  176. });
  177. }
  178. }
  179. /**
  180. * Checks if the local track supports this effect.
  181. *
  182. * @param {JitsiLocalTrack} jitsiLocalTrack - Track to apply effect
  183. *
  184. * @returns {boolean} Returns true if this effect can run on the specified track
  185. * false otherwise
  186. */
  187. isEnabled(jitsiLocalTrack) {
  188. return jitsiLocalTrack.isVideoTrack();
  189. }
  190. }
  191. /**
  192. * Creates a new instance of JitsiStreamBlurEffect.
  193. *
  194. * @returns {Promise<JitsiStreamBlurEffect>}
  195. */
  196. export function createBlurEffect() {
  197. return bpModelPromise
  198. .then(bpmodel =>
  199. Promise.resolve(new JitsiStreamBlurEffect(bpmodel))
  200. )
  201. .catch(error => {
  202. logger.error('Failed to load BodyPix model. Fallback to original stream!', error);
  203. throw error;
  204. });
  205. }