在flutter中通过MethodChannel调用java层的OpenGL函数渲染图形

1,209 阅读2分钟

引言

在flutter中如果只是想简单地播放视频,可以使用flutter官方提供的video_player插件,或者其他第三方提供的插件。但是如果想在flutter上渲染实时视频流,添加美颜算法、渲染全景视频等,这些插件可能不一定能满足我们的要求。遇到这种问题,一般要自己开发插件渲染视频或者使用第三方(比如声网)提供的sdk。如果选择自己开发插件,我们可以参考video_player的源码,结合MethodChannel,在java层或者c++层使用OpenGL作渲染。本文选择在java层使用OpenGL,如果要使用C++,在Java层通过NDK调用C++接口即可。

步骤

  • 创建flutter插件
  • 使用MethodChannel与Java层交互

在dart层添加初始化函数,每个渲染窗口都对应一个textureId。

class OpenglPlg {
  static const MethodChannel _channel =
  const MethodChannel('opengl_plg');
 
  int textureId = -1;
 
  Future<int> initialize(double width, double height) async {
    textureId = await _channel.invokeMethod('create', {
      'width': width,
      'height': height,
    });
    return textureId;
  }
 
  Future<Null> dispose() =>
      _channel.invokeMethod('dispose', {'textureId': textureId});
 
  bool get isInitialized => textureId != null;
}

初始化完成后可以在flutter app中使用,例如:

 
void main() {
  runApp(MyApp());
}
 
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
 
class _MyAppState extends State<MyApp> {
  final _controller = new OpenglPlg();
  final _width = 200.0;
  final _height = 200.0;
 
  @override
  initState() {
    super.initState();
    initializeController();
  }
 
  @override
  void dispose() {
    _controller.dispose();
 
    super.dispose();
  }
 
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('OpenGL via Texture widget example'),
        ),
        body: new Center(
          child: new Container(
            width: _width,
            height: _height,
            child: _controller.isInitialized
                ? new Texture(textureId: _controller.textureId)
                : null,
          ),
        ),
      ),
    );
  }
 
  Future<Null> initializeController() async {
    await _controller.initialize(_width, _height);
    setState(() {});
  }
}

完成dart层的工作后,便要开始着手完成java层的代码,在java层创建与dart层中Texture对应的SurfaceTexture。如果要兼容安卓与ios平台,需要分别完成各自平台对应的代码,这里只完成安卓平台的部分。关键代码为使用TextureRegistry类中SurfaceTextureEntry方法,创造一个SurfaceTextureEntry。这个SurfaceTextureEntry可以为Texture Widget提供textureId以及对应的SurfaceTexture。关键代码如下,其中onMethodCall即为使用MethodChannel时,需要重载的函数。

  // my textures
  private  TextureRegistry textures ;
  private LongSparseArray<OpenGLRenderer> renders = new LongSparseArray<>();
 
  // ...
 
  @Override
  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
 
    Map<String, Number> arguments = (Map<String, Number>) call.arguments;
    if (call.method.equals("create")) {
      TextureRegistry.SurfaceTextureEntry entry = textures.createSurfaceTexture();
      SurfaceTexture surfaceTexture = entry.surfaceTexture();
 
      int width = arguments.get("width").intValue();
      int height = arguments.get("height").intValue();
      surfaceTexture.setDefaultBufferSize(width, height);
 
      SampleRenderWorker worker = new SampleRenderWorker();
      OpenGLRenderer render = new OpenGLRenderer(surfaceTexture, worker);
      renders.put(entry.id(), render);
 
      result.success(entry.id());
    } else if (call.method.equals("dispose")) {
      long textureId = arguments.get("textureId").longValue();
      OpenGLRenderer render = renders.get(textureId);
      render.onDispose();
 
      renders.delete(textureId);
    } else {
      result.notImplemented();
    }
  }

创建完SurfaceTexture后便可以开始完成OpenGL部分:

public class OpenGLRenderer implements Runnable {
  protected final SurfaceTexture texture;
  private EGL10 egl;
  private EGLDisplay eglDisplay;
  private EGLContext eglContext;
  private EGLSurface eglSurface;

  private boolean running;

  private Worker worker;

  // ...

  @Override
  public void run() {
      initGL();
      worker.onCreate();
      Log.d(LOG_TAG, "OpenGL init OK.");

      while (running) {
          long loopStart = System.currentTimeMillis();

          if (worker.onDraw()) {
              if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) {
                  Log.d(LOG_TAG, String.valueOf(egl.eglGetError()));
              }
          }

          long waitDelta = 16 - (System.currentTimeMillis() - loopStart);
          if (waitDelta > 0) {
              try {
                  Thread.sleep(waitDelta);
              } catch (InterruptedException e) {
              }
          }
      }

      worker.onDispose();
      deinitGL();
  }

  private void initGL() {
      egl = (EGL10) EGLContext.getEGL();
      eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
      if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
          throw new RuntimeException("eglGetDisplay failed");
      }

      int[] version = new int[2];
      if (!egl.eglInitialize(eglDisplay, version)) {
          throw new RuntimeException("eglInitialize failed");
      }

      EGLConfig eglConfig = chooseEglConfig();
      eglContext = createContext(egl, eglDisplay, eglConfig);

      eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, texture, null);

      if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) {
          throw new RuntimeException("GL Error: " + GLUtils.getEGLErrorString(egl.eglGetError()));
      }

      if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
          throw new RuntimeException("GL make current error: " + GLUtils.getEGLErrorString(egl.eglGetError()));
      }
  }

  // ...

  public interface Worker {
      void onCreate();

      boolean onDraw();

      void onDispose();
  }
}
class SampleRenderWorker implements OpenGLRenderer.Worker {
 
    private double _tick = 0;
 
    @Override
    public boolean onDraw() {
        _tick = _tick + Math.PI / 60;
 
        float green = (float) ((Math.sin(_tick) + 1) / 2);
 
        GLES20.glClearColor(0f, green, 0f, 1f);
 
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        return true;
    }
}

至此,完成所有工作,运行app:

opengl.gif

完整代码:github.com/obweix/open…

参考:medium.com/@german_sap…