您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

JitsiStreamBlurEffect.js 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import { drawBokehEffect } from '@tensorflow-models/body-pix';
  2. import {
  3. CLEAR_INTERVAL,
  4. INTERVAL_TIMEOUT,
  5. SET_INTERVAL,
  6. timerWorkerScript
  7. } from './TimerWorker';
  8. /**
  9. * Represents a modified MediaStream that adds blur to video background.
  10. * <tt>JitsiStreamBlurEffect</tt> does the processing of the original
  11. * video stream.
  12. */
  13. export default class JitsiStreamBlurEffect {
  14. /**
  15. * Represents a modified video MediaStream track.
  16. *
  17. * @class
  18. * @param {BodyPix} bpModel - BodyPix model.
  19. */
  20. constructor(bpModel) {
  21. this._bpModel = bpModel;
  22. // Bind event handler so it is only bound once for every instance.
  23. this._onMaskFrameTimer = this._onMaskFrameTimer.bind(this);
  24. this._onVideoFrameTimer = this._onVideoFrameTimer.bind(this);
  25. this._outputCanvasElement = document.createElement('canvas');
  26. // Workaround for FF issue https://bugzilla.mozilla.org/show_bug.cgi?id=1388974
  27. this._outputCanvasElement.getContext('2d');
  28. this._maskCanvasElement = document.createElement('canvas');
  29. this._inputVideoElement = document.createElement('video');
  30. this._videoFrameTimerWorker = new Worker(timerWorkerScript);
  31. this._maskFrameTimerWorker = new Worker(timerWorkerScript);
  32. this._videoFrameTimerWorker.onmessage = this._onVideoFrameTimer;
  33. this._maskFrameTimerWorker.onmessage = this._onMaskFrameTimer;
  34. }
  35. /**
  36. * EventHandler onmessage for the videoFrameTimerWorker WebWorker.
  37. *
  38. * @private
  39. * @param {EventHandler} response - The onmessage EventHandler parameter.
  40. * @returns {void}
  41. */
  42. _onVideoFrameTimer(response) {
  43. if (response.data.id === INTERVAL_TIMEOUT) {
  44. this._renderVideo();
  45. }
  46. }
  47. /**
  48. * EventHandler onmessage for the maskFrameTimerWorker WebWorker.
  49. *
  50. * @private
  51. * @param {EventHandler} response - The onmessage EventHandler parameter.
  52. * @returns {void}
  53. */
  54. _onMaskFrameTimer(response) {
  55. if (response.data.id === INTERVAL_TIMEOUT) {
  56. this._renderMask();
  57. }
  58. }
  59. /**
  60. * Starts loop to capture video frame and render the segmentation mask.
  61. *
  62. * @param {MediaStream} stream - Stream to be used for processing.
  63. * @returns {MediaStream} - The stream with the applied effect.
  64. */
  65. startEffect(stream) {
  66. const firstVideoTrack = stream.getVideoTracks()[0];
  67. const { height, frameRate, width }
  68. = firstVideoTrack.getSettings ? firstVideoTrack.getSettings() : firstVideoTrack.getConstraints();
  69. this._frameRate = frameRate;
  70. this._height = height;
  71. this._width = width;
  72. this._outputCanvasElement.width = width;
  73. this._outputCanvasElement.height = height;
  74. this._maskCanvasElement.width = width;
  75. this._maskCanvasElement.height = height;
  76. this._maskCanvasContext = this._maskCanvasElement.getContext('2d');
  77. this._inputVideoElement.width = width;
  78. this._inputVideoElement.height = height;
  79. this._inputVideoElement.autoplay = true;
  80. this._inputVideoElement.srcObject = stream;
  81. this._videoFrameTimerWorker.postMessage({
  82. id: SET_INTERVAL,
  83. timeMs: 1000 / this._frameRate
  84. });
  85. this._maskFrameTimerWorker.postMessage({
  86. id: SET_INTERVAL,
  87. timeMs: 50
  88. });
  89. return this._outputCanvasElement.captureStream(this._frameRate);
  90. }
  91. /**
  92. * Stops the capture and render loop.
  93. *
  94. * @returns {void}
  95. */
  96. stopEffect() {
  97. this._videoFrameTimerWorker.postMessage({
  98. id: CLEAR_INTERVAL
  99. });
  100. this._maskFrameTimerWorker.postMessage({
  101. id: CLEAR_INTERVAL
  102. });
  103. }
  104. /**
  105. * Loop function to render the video frame input and draw blur effect.
  106. *
  107. * @private
  108. * @returns {void}
  109. */
  110. _renderVideo() {
  111. this._maskCanvasContext.drawImage(this._inputVideoElement, 0, 0, this._width, this._height);
  112. if (this._segmentationData) {
  113. drawBokehEffect(
  114. this._outputCanvasElement,
  115. this._inputVideoElement,
  116. this._segmentationData,
  117. 7, // Constant for background blur, integer values between 0-20
  118. 7 // Constant for edge blur, integer values between 0-20
  119. );
  120. // Make sure we clear this buffer before feeding the segmentation data
  121. // to drawBokehEffect for creating the blur. This fixes the memory leak
  122. // that started happening in WebGL in Chrome 77 and up.
  123. this._segmentationData = null;
  124. }
  125. }
  126. /**
  127. * Loop function to render the background mask.
  128. *
  129. * @private
  130. * @returns {void}
  131. */
  132. _renderMask() {
  133. this._bpModel.estimatePersonSegmentation(
  134. this._maskCanvasElement,
  135. 32, // Chose 32 for better performance
  136. 0.75 // Represents probability that a pixel belongs to a person
  137. )
  138. .then(value => {
  139. this._segmentationData = value;
  140. });
  141. }
  142. /**
  143. * Checks if the local track supports this effect.
  144. *
  145. * @param {JitsiLocalTrack} jitsiLocalTrack - Track to apply effect.
  146. * @returns {boolean} - Returns true if this effect can run on the specified track
  147. * false otherwise.
  148. */
  149. isEnabled(jitsiLocalTrack) {
  150. return jitsiLocalTrack.isVideoTrack() && jitsiLocalTrack.videoType === 'camera';
  151. }
  152. }