davidliu
Committed by GitHub

Manual bitmap frame capturer (#181)

@@ -62,7 +62,10 @@ internal constructor( @@ -62,7 +62,10 @@ internal constructor(
62 } 62 }
63 63
64 /** 64 /**
65 - * Creates a video track, recording video through the supplied [capturer] 65 + * Creates a video track, recording video through the supplied [capturer].
  66 + *
  67 + * This method will call [VideoCapturer.initialize] and handle the lifecycle of
  68 + * [SurfaceTextureHelper].
66 */ 69 */
67 fun createVideoTrack( 70 fun createVideoTrack(
68 name: String = "", 71 name: String = "",
  1 +import android.content.Context
  2 +import android.graphics.Bitmap
  3 +import android.graphics.Matrix
  4 +import android.graphics.Paint
  5 +import android.os.Build
  6 +import android.view.Surface
  7 +import org.webrtc.CapturerObserver
  8 +import org.webrtc.SurfaceTextureHelper
  9 +import org.webrtc.VideoCapturer
  10 +
  11 +/**
  12 + * A [VideoCapturer] that can be manually driven by passing in [Bitmap].
  13 + *
  14 + * Once [startCapture] is called, call [pushBitmap] to render images as video frames.
  15 + */
  16 +open class BitmapFrameCapturer : VideoCapturer {
  17 + private var surfaceTextureHelper: SurfaceTextureHelper? = null
  18 + private var capturerObserver: CapturerObserver? = null
  19 + private var disposed = false
  20 +
  21 + private var rotation = 0
  22 + private var width = 0
  23 + private var height = 0
  24 +
  25 + private val stateLock = Any()
  26 +
  27 + private var surface: Surface? = null
  28 +
  29 + override fun initialize(
  30 + surfaceTextureHelper: SurfaceTextureHelper,
  31 + context: Context,
  32 + observer: CapturerObserver,
  33 + ) {
  34 + synchronized(stateLock) {
  35 + this.surfaceTextureHelper = surfaceTextureHelper
  36 + this.capturerObserver = observer
  37 + surface = Surface(surfaceTextureHelper.surfaceTexture)
  38 + }
  39 + }
  40 +
  41 + private fun checkNotDisposed() {
  42 + check(!disposed) { "Capturer is disposed." }
  43 + }
  44 +
  45 + override fun startCapture(width: Int, height: Int, framerate: Int) {
  46 + synchronized(stateLock) {
  47 + checkNotDisposed()
  48 + checkNotNull(surfaceTextureHelper) { "BitmapFrameCapturer must be initialized before calling startCapture." }
  49 + capturerObserver?.onCapturerStarted(true)
  50 + surfaceTextureHelper?.startListening { frame -> capturerObserver?.onFrameCaptured(frame) }
  51 + }
  52 + }
  53 +
  54 + override fun stopCapture() {
  55 + synchronized(stateLock) {
  56 + surfaceTextureHelper?.stopListening()
  57 + capturerObserver?.onCapturerStopped()
  58 + }
  59 + }
  60 +
  61 + override fun changeCaptureFormat(width: Int, height: Int, framerate: Int) {
  62 + // Do nothing.
  63 + // These attributes are driven by the bitmaps fed in.
  64 + }
  65 +
  66 + override fun dispose() {
  67 + synchronized(stateLock) {
  68 + if (disposed) {
  69 + return
  70 + }
  71 +
  72 + stopCapture()
  73 + surface?.release()
  74 + disposed = true
  75 + }
  76 + }
  77 +
  78 + override fun isScreencast(): Boolean = false
  79 +
  80 + fun pushBitmap(bitmap: Bitmap, rotationDegrees: Int) {
  81 + synchronized(stateLock) {
  82 + if (disposed) {
  83 + return
  84 + }
  85 +
  86 + checkNotNull(surfaceTextureHelper)
  87 + checkNotNull(surface)
  88 + if (this.rotation != rotationDegrees) {
  89 + surfaceTextureHelper?.setFrameRotation(rotationDegrees)
  90 + this.rotation = rotationDegrees
  91 + }
  92 +
  93 + if (this.width != bitmap.width || this.height != bitmap.height) {
  94 + surfaceTextureHelper?.setTextureSize(bitmap.width, bitmap.height)
  95 + this.width = bitmap.width
  96 + this.height = bitmap.height
  97 + }
  98 +
  99 + surfaceTextureHelper?.handler?.post {
  100 + val canvas = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  101 + surface?.lockHardwareCanvas()
  102 + } else {
  103 + surface?.lockCanvas(null)
  104 + }
  105 +
  106 + if (canvas != null) {
  107 + canvas.drawBitmap(bitmap, Matrix(), Paint())
  108 + surface?.unlockCanvasAndPost(canvas)
  109 + }
  110 + }
  111 + }
  112 + }
  113 +}