@@ -22,9 +22,10 @@ import org.webrtc.JavaI420Buffer
2222import org.webrtc.VideoFrame
2323import org.webrtc.VideoProcessor
2424import org.webrtc.VideoSink
25+ import org.webrtc.YuvHelper
2526import java.nio.ByteBuffer
2627
27- class BackgroundBlurFrameProcessor (val context : Context , val isFrontFacing : Boolean ) :
28+ class BackgroundBlurFrameProcessor (val context : Context ) :
2829 VideoProcessor ,
2930 ImageSegmenterHelper .SegmenterListener {
3031
@@ -92,9 +93,13 @@ class BackgroundBlurFrameProcessor(val context: Context, val isFrontFacing: Bool
9293
9394 val finalFrame = blurredMat.toVideoFrame(resultBundle.inferenceTime)
9495
96+ if (finalFrame == null ) {
97+ Log .e(TAG , " Frame was null" )
98+ }
99+
95100 sink?.onFrame(finalFrame)
96101
97- finalFrame.release()
102+ finalFrame? .release()
98103 } finally {
99104 frameMat.release()
100105 blurredMat.release()
@@ -132,6 +137,7 @@ class BackgroundBlurFrameProcessor(val context: Context, val isFrontFacing: Bool
132137
133138 try {
134139 // weirdly rotation is 270 degree in portrait and 180 degree in landscape, no idea why
140+ // regardless if this behavior is device dependant, this calculation should correct the orientation
135141 val angle = ROT_360 - videoFrame.rotation.toDouble()
136142 rotationMat = Imgproc .getRotationMatrix2D(
137143 center,
@@ -166,107 +172,90 @@ class BackgroundBlurFrameProcessor(val context: Context, val isFrontFacing: Bool
166172 this .sink = sink
167173 }
168174
169- private fun Mat.toVideoFrame (time : Long ): VideoFrame {
170- val i420Mat = Mat ()
171- Imgproc .cvtColor(this , i420Mat, Imgproc .COLOR_RGBA2YUV_I420 )
172-
173- // Get the raw bytes from the new I420 Mat
174- val i420ByteArray = ByteArray ((i420Mat.total() * i420Mat.elemSize()).toInt())
175- i420Mat.get(0 , 0 , i420ByteArray)
176-
177- val width = this .width()
178- val height = this .height()
179-
180- val yPlaneSize = width * height
181- val uvPlaneSize = (width / 2 ) * (height / 2 )
182-
183- val yBuffer = ByteBuffer .allocateDirect(yPlaneSize)
184- val uBuffer = ByteBuffer .allocateDirect(uvPlaneSize)
185- val vBuffer = ByteBuffer .allocateDirect(uvPlaneSize)
186-
187- yBuffer.put(i420ByteArray, 0 , yPlaneSize)
188- uBuffer.put(i420ByteArray, yPlaneSize, uvPlaneSize)
189- vBuffer.put(i420ByteArray, yPlaneSize + uvPlaneSize, uvPlaneSize)
190-
191- yBuffer.rewind()
192- uBuffer.rewind()
193- vBuffer.rewind()
194-
195- // Create the I420Buffer using the separate planes
196- val finalFrameBuffer = JavaI420Buffer .wrap(
197- width,
198- height,
199- yBuffer,
200- width,
201- uBuffer,
202- width / 2 ,
203- vBuffer,
204- width / 2 ,
205- null
206- )
175+ private fun Mat.toVideoFrame (time : Long ): VideoFrame ? =
176+ runCatching {
177+ val i420Mat = Mat ()
178+ Imgproc .cvtColor(this , i420Mat, Imgproc .COLOR_RGBA2YUV_I420 )
207179
208- i420Mat.release()
209-
210- return VideoFrame (finalFrameBuffer, 0 , time)
211- }
180+ // Get the raw bytes from the new I420 Mat to i420ByteArray
181+ val i420ByteArray = ByteArray ((i420Mat.total() * i420Mat.elemSize()).toInt())
182+ i420Mat.get(0 , 0 , i420ByteArray)
212183
213- private fun VideoFrame.I420Buffer.toMat (): Mat ? =
214- kotlin.runCatching {
215- val i420Buffer = this
184+ val width = this .width()
185+ val height = this .height()
216186
217- val width = i420Buffer.width
218- val height = i420Buffer.height
219187 val yPlaneSize = width * height
188+ val uvPlaneSize = (width / 2 ) * (height / 2 )
189+
190+ val yBuffer = ByteBuffer .allocateDirect(yPlaneSize)
191+ val uBuffer = ByteBuffer .allocateDirect(uvPlaneSize)
192+ val vBuffer = ByteBuffer .allocateDirect(uvPlaneSize)
193+
194+ yBuffer.put(i420ByteArray, 0 , yPlaneSize)
195+ uBuffer.put(i420ByteArray, yPlaneSize, uvPlaneSize)
196+ vBuffer.put(i420ByteArray, yPlaneSize + uvPlaneSize, uvPlaneSize)
197+
198+ yBuffer.rewind()
199+ uBuffer.rewind()
200+ vBuffer.rewind()
201+
202+ // Create the I420Buffer using the separate planes
203+ val finalFrameBuffer = JavaI420Buffer .wrap(
204+ width,
205+ height,
206+ yBuffer,
207+ width,
208+ uBuffer,
209+ width / 2 ,
210+ vBuffer,
211+ width / 2 ,
212+ null
213+ )
220214
221- val nv21Height = (height * NV21_HEIGHT_MULTI ).toInt()
222- val nv21Width = width
223- val nv21Size = nv21Height * nv21Width
224- val nv21Data = ByteArray (nv21Size)
225-
226- val dataY = i420Buffer.dataY
227- val dataU = i420Buffer.dataU
228- val dataV = i420Buffer.dataV
215+ i420Mat.release()
229216
230- val strideY = i420Buffer.strideY // Likely equal to the width, but not always, depending on mem alignment
231- val strideU = i420Buffer.strideU // U and V have identical dimens and strides
232- val strideV = i420Buffer.strideV
217+ return VideoFrame (finalFrameBuffer, 0 , time)
218+ }.getOrElse { throwable ->
219+ Log .e( TAG , " Error in Mat.toVideoFrame $throwable " )
233220
234- if (strideY == width) {
235- // Fast path: contiguous data
236- dataY.get(nv21Data, 0 , yPlaneSize)
237- } else {
238- // Slow path: row-by-row copy
239- for (row in 0 until height) {
240- dataY.position(row * strideY)
241- dataY.get(nv21Data, row * width, width)
242- }
243- }
221+ null
222+ }
244223
245- val vuPlaneOffset = width * height
246- for (row in 0 until height / 2 ) {
247- for (col in 0 until width / 2 ) {
248- // Get U and V values from their respective planes using row/col/stride
249- val v = dataV[row * strideV + col]
250- val u = dataU[row * strideU + col]
251-
252- // Put them into the NV21 buffer (V, then U)
253- val nv21Index = vuPlaneOffset + (row * width) + (col * 2 )
254- nv21Data[nv21Index] = v
255- nv21Data[nv21Index + 1 ] = u
256- }
257- }
224+ private fun VideoFrame.I420Buffer.toMat (): Mat ? =
225+ runCatching {
226+ val chromaWidth = (width + 1 ) / 2
227+ val chromaHeight = (height + 1 ) / 2
228+ val minSize = width * height + chromaWidth * chromaHeight * 2
229+
230+ val nv12ByteBuffer = ByteBuffer .allocateDirect(minSize)
231+ YuvHelper .I420ToNV12 (
232+ this .dataY,
233+ this .strideY,
234+ this .dataU,
235+ this .strideU,
236+ this .dataV,
237+ this .strideV,
238+ nv12ByteBuffer,
239+ width,
240+ height
241+ )
258242
259243 val mat = Mat (
260- nv21Height ,
261- nv21Width ,
244+ (height * NV21_HEIGHT_MULTI ).toInt() ,
245+ width ,
262246 CvType .CV_8UC1 // 8 bit unsigned 1 channel
263247 )
264248
265- mat.put(0 , 0 , nv21Data)
266- Imgproc .cvtColor(mat, mat, Imgproc .COLOR_YUV2RGBA_NV21 )
249+ mat.put(0 , 0 , nv12ByteBuffer.array())
267250
268- i420Buffer.release()
251+ Imgproc .cvtColor(mat, mat, Imgproc .COLOR_YUV2RGBA_NV12 )
252+
253+ this .release()
269254
270255 mat
271- }.getOrNull()
256+ }.getOrElse { throwable ->
257+ Log .e(TAG , " Error in VideoFrame.I420Buffer.toMat $throwable " )
258+
259+ null
260+ }
272261}
0 commit comments