引言
在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:
完整代码:https://github.com/obweix/opengl_plugin
参考:https://medium.com/@german_saprykin/opengl-with-texture-widget-f919743d25d9