将相机渲染到多个表面 - 屏幕上和屏幕外
Rendering camera into multiple surfaces - on and off screen
我想将相机输出渲染到一个视图中,并偶尔将相机输出帧保存到一个文件中,约束条件是 - 保存的帧应该是相同的分辨率 因为相机已配置,而视图 小于相机输出 (保持纵横比)。
基于 ContinuousCaptureActivity example in grafika,我认为最好的方法是将相机发送到 SurfaceTexture
并通常渲染输出并将其缩小为 SurfaceView
,并且当需要时,将整个帧渲染到另一个没有视图的 Surface
中,以便与常规 SurfaceView
渲染并行地从中检索字节缓冲区。
该示例与我的情况非常相似 - 预览呈现为较小尺寸的视图,并且可以通过 VideoEncoder
.
以全分辨率进行记录和保存
我用自己的逻辑替换了 VideoEncoder
逻辑,但在尝试提供 Surface
时遇到困难,就像编码器一样,用于全分辨率渲染。如何创建这样的 Surface
?我的做法正确吗?
基于示例的一些代码思路:
surfaceCreated(SurfaceHolder holder)
方法内部(第 350 行):
@Override // SurfaceHolder.Callback
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "surfaceCreated holder=" + holder);
mEglCore = new EglCore(null, EglCore.FLAG_RECORDABLE);
mDisplaySurface = new WindowSurface(mEglCore, holder.getSurface(), false);
mDisplaySurface.makeCurrent();
mFullFrameBlit = new FullFrameRect(
new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));
mTextureId = mFullFrameBlit.createTextureObject();
mCameraTexture = new SurfaceTexture(mTextureId);
mCameraTexture.setOnFrameAvailableListener(this);
Log.d(TAG, "starting camera preview");
try {
mCamera.setPreviewTexture(mCameraTexture);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
mCamera.startPreview();
// *** MY EDIT START ***
// Encoder creation no longer needed
// try {
// mCircEncoder = new CircularEncoder(VIDEO_WIDTH, VIDEO_HEIGHT, 6000000,
// mCameraPreviewThousandFps / 1000, 7, mHandler);
// } catch (IOException ioe) {
// throw new RuntimeException(ioe);
// }
mEncoderSurface = new WindowSurface(mEglCore, mCameraTexture); // <-- Crashes with EGL error 0x3003
// *** MY EDIT END ***
updateControls();
}
drawFrame()
方法(第 420 行):
private void drawFrame() {
//Log.d(TAG, "drawFrame");
if (mEglCore == null) {
Log.d(TAG, "Skipping drawFrame after shutdown");
return;
}
// Latch the next frame from the camera.
mDisplaySurface.makeCurrent();
mCameraTexture.updateTexImage();
mCameraTexture.getTransformMatrix(mTmpMatrix);
// Fill the SurfaceView with it.
SurfaceView sv = (SurfaceView) findViewById(R.id.continuousCapture_surfaceView);
int viewWidth = sv.getWidth();
int viewHeight = sv.getHeight();
GLES20.glViewport(0, 0, viewWidth, viewHeight);
mFullFrameBlit.drawFrame(mTextureId, mTmpMatrix);
mDisplaySurface.swapBuffers();
// *** MY EDIT START ***
// Send it to the video encoder.
if (someCondition) {
mEncoderSurface.makeCurrent();
GLES20.glViewport(0, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
mFullFrameBlit.drawFrame(mTextureId, mTmpMatrix);
mEncoderSurface.swapBuffers();
try {
mEncoderSurface.saveFrame(new File(getExternalFilesDir(null), String.valueOf(System.currentTimeMillis()) + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
}
// *** MY EDIT END ***
}
你走在正确的轨道上。 SurfaceTexture 只是快速环绕相机的原始 YUV 帧,因此 "external" 纹理是原始图像,没有任何变化。您无法直接从外部纹理中读取像素,因此您必须先将其渲染到某个地方。
最简单的方法是创建一个离屏 pbuffer 表面。 Grafika 的 gles/OffscreenSurface class 正是这样做的(通过调用 eglCreatePbufferSurface()
)。使该 EGLSurface 当前,将纹理渲染到 FullFrameRect 上,然后使用 glReadPixels()
读取帧缓冲区(请参阅 EglSurfaceBase#saveFrame()
了解代码)。不要打电话给 eglSwapBuffers()
.
请注意,您没有为输出创建 Android Surface,只是一个 EGLSurface。 (They're different.)
我想将相机输出渲染到一个视图中,并偶尔将相机输出帧保存到一个文件中,约束条件是 - 保存的帧应该是相同的分辨率 因为相机已配置,而视图 小于相机输出 (保持纵横比)。
基于 ContinuousCaptureActivity example in grafika,我认为最好的方法是将相机发送到 SurfaceTexture
并通常渲染输出并将其缩小为 SurfaceView
,并且当需要时,将整个帧渲染到另一个没有视图的 Surface
中,以便与常规 SurfaceView
渲染并行地从中检索字节缓冲区。
该示例与我的情况非常相似 - 预览呈现为较小尺寸的视图,并且可以通过 VideoEncoder
.
我用自己的逻辑替换了 VideoEncoder
逻辑,但在尝试提供 Surface
时遇到困难,就像编码器一样,用于全分辨率渲染。如何创建这样的 Surface
?我的做法正确吗?
基于示例的一些代码思路:
surfaceCreated(SurfaceHolder holder)
方法内部(第 350 行):
@Override // SurfaceHolder.Callback
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "surfaceCreated holder=" + holder);
mEglCore = new EglCore(null, EglCore.FLAG_RECORDABLE);
mDisplaySurface = new WindowSurface(mEglCore, holder.getSurface(), false);
mDisplaySurface.makeCurrent();
mFullFrameBlit = new FullFrameRect(
new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));
mTextureId = mFullFrameBlit.createTextureObject();
mCameraTexture = new SurfaceTexture(mTextureId);
mCameraTexture.setOnFrameAvailableListener(this);
Log.d(TAG, "starting camera preview");
try {
mCamera.setPreviewTexture(mCameraTexture);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
mCamera.startPreview();
// *** MY EDIT START ***
// Encoder creation no longer needed
// try {
// mCircEncoder = new CircularEncoder(VIDEO_WIDTH, VIDEO_HEIGHT, 6000000,
// mCameraPreviewThousandFps / 1000, 7, mHandler);
// } catch (IOException ioe) {
// throw new RuntimeException(ioe);
// }
mEncoderSurface = new WindowSurface(mEglCore, mCameraTexture); // <-- Crashes with EGL error 0x3003
// *** MY EDIT END ***
updateControls();
}
drawFrame()
方法(第 420 行):
private void drawFrame() {
//Log.d(TAG, "drawFrame");
if (mEglCore == null) {
Log.d(TAG, "Skipping drawFrame after shutdown");
return;
}
// Latch the next frame from the camera.
mDisplaySurface.makeCurrent();
mCameraTexture.updateTexImage();
mCameraTexture.getTransformMatrix(mTmpMatrix);
// Fill the SurfaceView with it.
SurfaceView sv = (SurfaceView) findViewById(R.id.continuousCapture_surfaceView);
int viewWidth = sv.getWidth();
int viewHeight = sv.getHeight();
GLES20.glViewport(0, 0, viewWidth, viewHeight);
mFullFrameBlit.drawFrame(mTextureId, mTmpMatrix);
mDisplaySurface.swapBuffers();
// *** MY EDIT START ***
// Send it to the video encoder.
if (someCondition) {
mEncoderSurface.makeCurrent();
GLES20.glViewport(0, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
mFullFrameBlit.drawFrame(mTextureId, mTmpMatrix);
mEncoderSurface.swapBuffers();
try {
mEncoderSurface.saveFrame(new File(getExternalFilesDir(null), String.valueOf(System.currentTimeMillis()) + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
}
// *** MY EDIT END ***
}
你走在正确的轨道上。 SurfaceTexture 只是快速环绕相机的原始 YUV 帧,因此 "external" 纹理是原始图像,没有任何变化。您无法直接从外部纹理中读取像素,因此您必须先将其渲染到某个地方。
最简单的方法是创建一个离屏 pbuffer 表面。 Grafika 的 gles/OffscreenSurface class 正是这样做的(通过调用 eglCreatePbufferSurface()
)。使该 EGLSurface 当前,将纹理渲染到 FullFrameRect 上,然后使用 glReadPixels()
读取帧缓冲区(请参阅 EglSurfaceBase#saveFrame()
了解代码)。不要打电话给 eglSwapBuffers()
.
请注意,您没有为输出创建 Android Surface,只是一个 EGLSurface。 (They're different.)