glReadPixels 在 WebRTC Android SurfaceViewRenderer 中不起作用
glReadPixels does not work in WebRTC Android SurfaceViewRenderer
我正在尝试从 WebRTC Android 应用程序保存帧。在 SurfaceViewRenderer.java it draws YUV frame using GLES shader. To save the drawn frame I added saveFrame() from grafika 到 SurfaceViewRenderer.java 中,但是当我调用 saveFrame() 时它不起作用(位图为空)。我以为 glReadPixels() 从当前颜色缓冲区读取像素,但它似乎没有在当前 EGLcontext 中调用?如何调用glReadPixels()保存当前帧?
我写的代码是这样的:
在 MainActivity 中我添加了这样的按钮。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bitmap = localSurfaceViewRender.saveFrame();
if (bitmap != null) {
Toast.makeText(getApplicationContext(), "Saved!!", Toast.LENGTH_SHORT).show();
imageView.setImageBitmap(bitmap);
}
}
});
在 SurfaceViewRenderer.java 中,我添加了一些方法来从当前绑定的 egl
中获取值
public int getSurfaceHeight() {
if (mHeight < 0) {
return eglBase.surfaceHeight();
} else {
return mHeight;
}
}
public int getSurfaceWidth() {
if (mWidth < 0) {
return eglBase.surfaceWidth();
} else {
return mWidth;
}
}
public Bitmap saveFrame() {
int width = eglBase.surfaceWidth();
int height = eglBase.surfaceHeight();
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glFinish();
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, buf);
GlUtil.checkNoGLES2Error("glReadPixels");
buf.rewind();
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
return bmp;
}
编辑:
这段代码是saveFrame
的完整代码
// save as a file
public void saveFrame(final File file) {
runOnRenderThread(new Runnable() {
@Override
public void run() {
int width = eglBase.surfaceWidth();
int height = eglBase.surfaceHeight();
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glFinish();
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
GlUtil.checkNoGLES2Error("glReadPixels");
buf.rewind();
// fliping by vertical
byte[] tmp = new byte[width * 4];
for (int i = 0; i < height / 2; i++) {
buf.get(tmp);
System.arraycopy(buf.array(), buf.limit() - buf.position(),
buf.array(), buf.position() - width * 4, width * 4);
System.arraycopy(tmp, 0, buf.array(), buf.limit() - buf.position(), width * 4);
}
buf.rewind();
String filename = file.toString();
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(filename));
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
bmp.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bmp.recycle();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (bos != null) try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
}
});
}
glReadPixels()
函数从当前上下文中读取帧缓冲区的内容。一旦调用 eglSwapBuffers()
,内容就消失了。
换句话说,应用无法从 SurfaceView 中读取任何内容。因此,您必须在将缓冲区提交给合成器之前读出像素。
(我不确定如果你在交换缓冲区后立即调用 glReadPixels()
会发生什么......你可能会从回收缓冲区中读取先前发送的帧,但你不能永远依赖它。)
您似乎是从 UI 主线程调用 glReadPixels()
。如果 SurfaceView 渲染发生在不同的线程上,您将需要从同一个线程访问帧缓冲区,因为这是 EGL 上下文当前所在的位置。给定的上下文不能同时在多个线程中处于当前状态。
我正在尝试从 WebRTC Android 应用程序保存帧。在 SurfaceViewRenderer.java it draws YUV frame using GLES shader. To save the drawn frame I added saveFrame() from grafika 到 SurfaceViewRenderer.java 中,但是当我调用 saveFrame() 时它不起作用(位图为空)。我以为 glReadPixels() 从当前颜色缓冲区读取像素,但它似乎没有在当前 EGLcontext 中调用?如何调用glReadPixels()保存当前帧?
我写的代码是这样的:
在 MainActivity 中我添加了这样的按钮。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bitmap = localSurfaceViewRender.saveFrame();
if (bitmap != null) {
Toast.makeText(getApplicationContext(), "Saved!!", Toast.LENGTH_SHORT).show();
imageView.setImageBitmap(bitmap);
}
}
});
在 SurfaceViewRenderer.java 中,我添加了一些方法来从当前绑定的 egl
中获取值public int getSurfaceHeight() {
if (mHeight < 0) {
return eglBase.surfaceHeight();
} else {
return mHeight;
}
}
public int getSurfaceWidth() {
if (mWidth < 0) {
return eglBase.surfaceWidth();
} else {
return mWidth;
}
}
public Bitmap saveFrame() {
int width = eglBase.surfaceWidth();
int height = eglBase.surfaceHeight();
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glFinish();
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, buf);
GlUtil.checkNoGLES2Error("glReadPixels");
buf.rewind();
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
return bmp;
}
编辑: 这段代码是saveFrame
的完整代码// save as a file
public void saveFrame(final File file) {
runOnRenderThread(new Runnable() {
@Override
public void run() {
int width = eglBase.surfaceWidth();
int height = eglBase.surfaceHeight();
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glFinish();
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
GlUtil.checkNoGLES2Error("glReadPixels");
buf.rewind();
// fliping by vertical
byte[] tmp = new byte[width * 4];
for (int i = 0; i < height / 2; i++) {
buf.get(tmp);
System.arraycopy(buf.array(), buf.limit() - buf.position(),
buf.array(), buf.position() - width * 4, width * 4);
System.arraycopy(tmp, 0, buf.array(), buf.limit() - buf.position(), width * 4);
}
buf.rewind();
String filename = file.toString();
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(filename));
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
bmp.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bmp.recycle();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (bos != null) try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
}
});
}
glReadPixels()
函数从当前上下文中读取帧缓冲区的内容。一旦调用 eglSwapBuffers()
,内容就消失了。
换句话说,应用无法从 SurfaceView 中读取任何内容。因此,您必须在将缓冲区提交给合成器之前读出像素。
(我不确定如果你在交换缓冲区后立即调用 glReadPixels()
会发生什么......你可能会从回收缓冲区中读取先前发送的帧,但你不能永远依赖它。)
您似乎是从 UI 主线程调用 glReadPixels()
。如果 SurfaceView 渲染发生在不同的线程上,您将需要从同一个线程访问帧缓冲区,因为这是 EGL 上下文当前所在的位置。给定的上下文不能同时在多个线程中处于当前状态。