Android AVDemo(13):视频渲染,代码开源并提供解析
介绍 Android 视频渲染流程和原理,并提供 Demo 源码和解析。
本文转自微信公众号
关键帧Keyframe
,推荐您关注来获取音视频、AI 领域的最新技术和产品信息:您还可以加入知识星球
关键帧的音视频开发圈
来一起交流工作中的技术难题、职场经验:
iOS/Android 客户端开发同学如果想要开始学习音视频开发,最丝滑的方式是对音视频基础概念知识有一定了解后,再借助 iOS/Android 平台的音视频能力上手去实践音视频的采集 → 编码 → 封装 → 解封装 → 解码 → 渲染
过程,并借助音视频实用工具来分析和理解对应的音视频数据。
在音视频工程示例这个栏目,我们将通过拆解采集 → 编码 → 封装 → 解封装 → 解码 → 渲染
流程并实现 Demo 来向大家介绍如何在 iOS/Android 平台上手音视频开发。
这里是 Android 第十三篇:Android 视频渲染 Demo。这个 Demo 里包含以下内容:
- 1)实现一个视频采集装模块;
- 2)实现一个视频渲染模块;
- 3)串联视频采集和渲染模块,将采集的视频数据输入给渲染模块进行渲染;
- 4)详尽的代码注释,帮你理解代码逻辑和原理。
在本文中,我们将详解一下 Demo 的具体实现和源码。读完本文内容相信就能帮你掌握相关知识。
不过,如果你的需求是:1)直接获得全部工程源码;2)想进一步咨询音视频技术问题;3)咨询音视频职业发展问题。可以根据自己的需要考虑是否加入『关键帧的音视频开发圈』。
1、视频采集模块
在这个 Demo 中,视频采集模块 KFVideoCapture
的实现与《Android 视频采集 Demo》中一样,这里就不再重复介绍了,其接口如下:
KFIVideoCapture.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface KFIVideoCapture {
///< 视频采集初始化。
public void setup(Context context, KFVideoCaptureConfig config, KFVideoCaptureListener listener, EGLContext eglShareContext);
///< 释放采集实例。
public void release();
///< 开始采集。
public void startRunning();
///< 关闭采集。
public void stopRunning();
///< 是否正在采集。
public boolean isRunning();
///< 获取 OpenGL 上下文。
public EGLContext getEGLContext();
///< 切换摄像头。
public void switchCamera();
}
2、视频渲染模块
在之前的《Android 视频采集 Demo》那篇中,我们采集后的视频数据是通过 KFRenderView
来做预览渲染的。这篇我们来介绍一下使用 KFRenderView
内部管理 KFSurfaceView
、KFTextureView
,并且使用 OpenGL 实现渲染功能。
首先,我们在 KFRenderListener
中定义渲染回调。
KFRenderListener.java
1
2
3
4
5
public interface KFRenderListener {
void surfaceCreate(@NonNull Surface surface); ///< 渲染缓存创建.
void surfaceChanged(@NonNull Surface surface, int width, int height); ///< 渲染缓存变更分辨率。
void surfaceDestroy(@NonNull Surface surface); ///< 渲染缓存销毁。
}
然后,我们在 KFRenderView
中管理 KFSurfaceView
、KFTextureView
以及具体渲染逻辑。
KFRenderView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
public class KFRenderView extends ViewGroup {
private KFGLContext mEGLContext = null; ///< OpenGL 上下文。
private KFGLFilter mFilter = null; ///< 特效渲染到指定 Surface。
private EGLContext mShareContext = null; ///< 共享上下文。
private View mRenderView = null; ///< 渲染视图基类。
private int mSurfaceWidth = 0; ///< 渲染缓存宽。
private int mSurfaceHeight = 0; ///< 渲染缓存高。
private FloatBuffer mSquareVerticesBuffer = null; ///< 自定义顶点.
private KFRenderMode mRenderMode = KFRenderMode.KFRenderModeFill; ///< 自适应模式,黑边,比例填冲。
private boolean mSurfaceChanged = false; ///< 渲染缓存是否变更。
private Size mLastRenderSize = new Size(0,0); ///< 标记上次渲染 Size。
public enum KFRenderMode {
KFRenderStretch, ///< 拉伸满,可能变形。
KFRenderModeFit, ///< 黑边。
KFRenderModeFill ///< 比例填充。
};
public KFRenderView(Context context, EGLContext eglContext) {
super(context);
mShareContext = eglContext; ///< 共享上下文。
_setupSquareVertices(); ///< 初始化顶点。
boolean isSurfaceView = false; ///< TextureView 与 SurfaceView 开关。
if (isSurfaceView) {
mRenderView = new KFSurfaceView(context, mListener);
} else {
mRenderView = new KFTextureView(context, mListener);
}
this.addView(mRenderView);// 添加视图到父视图
}
public void release() {
///< 释放 GL 上下文、特效。
if (mEGLContext != null) {
mEGLContext.bind();
if (mFilter != null){
mFilter.release();
mFilter = null;
}
mEGLContext.unbind();
mEGLContext.release();
mEGLContext = null;
}
}
public void render(KFTextureFrame inputFrame) {
if (inputFrame == null) {
return;
}
///< 输入纹理使用自定义特效渲染到 View 的 Surface 上。
if (mEGLContext != null && mFilter != null) {
boolean frameResolutionChanged = inputFrame.textureSize.getWidth() != mLastRenderSize.getWidth() || inputFrame.textureSize.getHeight() != mLastRenderSize.getHeight();
///< 渲染缓存变更或者视图大小变更重新设置顶点。
if (mSurfaceChanged || frameResolutionChanged) {
_recalculateVertices(inputFrame.textureSize);
mSurfaceChanged = false;
mLastRenderSize = inputFrame.textureSize;
}
///< 渲染到指定 Surface。
mEGLContext.bind();
mFilter.setSquareVerticesBuffer(mSquareVerticesBuffer);
GLES20.glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);
mFilter.render(inputFrame);
mEGLContext.swapBuffers();
mEGLContext.unbind();
}
}
private KFRenderListener mListener = new KFRenderListener() {
@Override
///< 渲染缓存创建。
public void surfaceCreate(@NonNull Surface surface) {
mEGLContext = new KFGLContext(mShareContext,surface);
///< 初始化特效。
mEGLContext.bind();
_setupFilter();
mEGLContext.unbind();
}
@Override
///< 渲染缓存变更。
public void surfaceChanged(@NonNull Surface surface, int width, int height) {
mSurfaceWidth = width;
mSurfaceHeight = height;
mSurfaceChanged = true;
///< 设置 GL 上下文 Surface。
mEGLContext.bind();
mEGLContext.setSurface(surface);
mEGLContext.unbind();
}
@Override
public void surfaceDestroy(@NonNull Surface surface) {
}
};
private void _setupFilter() {
///< 初始化特效。
if (mFilter == null) {
mFilter = new KFGLFilter(true, KFGLBase.defaultVertexShader,KFGLBase.defaultFragmentShader);
}
}
private void _setupSquareVertices() {
///< 初始化顶点缓存。
final float squareVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
ByteBuffer squareVerticesByteBuffer = ByteBuffer.allocateDirect(4 * squareVertices.length);
squareVerticesByteBuffer.order(ByteOrder.nativeOrder());
mSquareVerticesBuffer = squareVerticesByteBuffer.asFloatBuffer();
mSquareVerticesBuffer.put(squareVertices);
mSquareVerticesBuffer.position(0);
}
private void _recalculateVertices(Size inputImageSize) {
///< 按照适应模式创建顶点。
if (mSurfaceWidth == 0 || mSurfaceHeight == 0) {
return;
}
Size renderSize = new Size(mSurfaceWidth,mSurfaceHeight);
float heightScaling = 1, widthScaling = 1;
Size insetSize = new Size(0,0);
float inputAspectRatio = (float) inputImageSize.getWidth() / (float)inputImageSize.getHeight();
float outputAspectRatio = (float)renderSize.getWidth() / (float)renderSize.getHeight();
boolean isAutomaticHeight = inputAspectRatio <= outputAspectRatio ? false : true;
if (isAutomaticHeight) {
float insetSizeHeight = (float)inputImageSize.getHeight() / ((float)inputImageSize.getWidth() / (float)renderSize.getWidth());
insetSize = new Size(renderSize.getWidth(),(int)insetSizeHeight);
} else {
float insetSizeWidth = (float)inputImageSize.getWidth() / ((float)inputImageSize.getHeight() / (float)renderSize.getHeight());
insetSize = new Size((int)insetSizeWidth,renderSize.getHeight());
}
switch (mRenderMode) {
case KFRenderStretch: {
widthScaling = 1;
heightScaling = 1;
}; break;
case KFRenderModeFit: {
widthScaling = (float)insetSize.getWidth() / (float)renderSize.getWidth();
heightScaling = (float)insetSize.getHeight() / (float)renderSize.getHeight();
}; break;
case KFRenderModeFill: {
widthScaling = (float) renderSize.getHeight() / (float)insetSize.getHeight();
heightScaling = (float)renderSize.getWidth() / (float)insetSize.getWidth();
}; break;
}
final float squareVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
final float customVertices[] = {
-widthScaling, -heightScaling,
widthScaling, -heightScaling,
-widthScaling, heightScaling,
widthScaling, heightScaling,
};
ByteBuffer squareVerticesByteBuffer = ByteBuffer.allocateDirect(4 * customVertices.length);
squareVerticesByteBuffer.order(ByteOrder.nativeOrder());
mSquareVerticesBuffer = squareVerticesByteBuffer.asFloatBuffer();
mSquareVerticesBuffer.put(customVertices);
mSquareVerticesBuffer.position(0);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
///< 视图变更 Size。
this.mRenderView.layout(left,top,right,bottom);
}
}
上面是 KFRenderView
的实现,继承自 ViewGroup,其中主要包含这几个部分:
- 1)管理
KFSurfaceView
、KFTextureView
。- 通过
isSurfaceView
开关来控制使用TextureView
或SurfaceView
。 SurfaceView
性能高一些,可以在自线程更新 UI 的 View,遵循双缓冲机制,但无法像正常视图实现动画。TextureView
性能稍微差一点,重载了 Draw 方法,可以像正常视图实现动画。
- 通过
- 2)创建 OpenGL 上下文。
- 这里需要注意的是,我们通过
mEGLContext
管理上下文,初始化方法需要输入渲染视图 Surface 作为参数,这样就可以将绘制结果直接渲染到指定 Surface。
- 这里需要注意的是,我们通过
- 3)创建渲染特效。
- 通过
mFilter
将纹理数据渲染到渲染视图 Surface。 - 通过
mRenderMode
控制自定义渲染比例,在方法_recalculateVertices
中实时计算顶点数据来实现。
- 通过
- 4)渲染回调通知 Surface 生命周期。
- 在
KFRenderListener
的surfaceCreate
回调中通知 Surface 创建。 - 在
KFRenderListener
的surfaceChanged
回调中通知 Surface 变更。 - 在
KFRenderListener
的surfaceDestroy
回调中通知 Surface 销毁。
- 在
接下来是内部渲染视图 KFSurfaceView
的实现,需要注意 Surface 是通过 surfaceHolder
方法 getSurface
获取:
KFSurfaceView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class KFSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private KFRenderListener mListener = null; ///< 回调。
private SurfaceHolder mHolder = null; ///< Surface 的抽象接口。
public KFSurfaceView(Context context, KFRenderListener listener) {
super(context);
mListener = listener;
getHolder().addCallback(this);
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
///< Surface 创建。
mHolder = surfaceHolder;
///< 根据 SurfaceHolder 创建 Surface。
if (mListener != null) {
mListener.surfaceCreate(surfaceHolder.getSurface());
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
///< Surface 分辨率变更。
if (mListener != null) {
mListener.surfaceChanged(surfaceHolder.getSurface(),width,height);
}
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
///< Surface 销毁。
if (mListener != null) {
mListener.surfaceDestroy(surfaceHolder.getSurface());
}
}
}
接下来是内部渲染视图 KFTextureView
的实现,需要注意 Surface 是通过 surfaceTexture
创建生成:
KFTextureView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class KFTextureView extends TextureView implements TextureView.SurfaceTextureListener {
private KFRenderListener mListener = null; ///< 回调。
private Surface mSurface = null; ///< 渲染缓存。
private SurfaceTexture mSurfaceTexture = null; ///< 纹理缓存。
public KFTextureView(Context context, KFRenderListener listener) {
super(context);
this.setSurfaceTextureListener(this);
mListener = listener;
}
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
///< 纹理缓存创建。
mSurfaceTexture = surfaceTexture;
///< 根据 SurfaceTexture 创建 Surface。
mSurface = new Surface(surfaceTexture);
if (mListener != null) {
///< 创建时候回调一次分辨率变更,对其 SurfaceView 接口。
mListener.surfaceCreate(mSurface);
mListener.surfaceChanged(mSurface,width,height);
}
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
///< 纹理缓存变更分辨率。
if (mListener != null) {
mListener.surfaceChanged(mSurface,width,height);
}
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
///< 纹理缓存销毁。
if (mListener != null) {
mListener.surfaceDestroy(mSurface);
}
if (mSurface != null) {
mSurface.release();
mSurface = null;
}
return false;
}
}
更具体细节见上述代码及其注释。
3、采集视频数据并渲染
我们在一个 MainActivity
中来实现对采集的视频数据进行渲染播放。
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class MainActivity extends AppCompatActivity {
private KFIVideoCapture mCapture; ///< 相机采集。
private KFVideoCaptureConfig mCaptureConfig; ///< 相机采集配置。
private KFRenderView mRenderView; ///< 渲染视图。
private KFGLContext mGLContext; ///< OpenGL 上下文。
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
///< 检测采集相关权限。
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions((Activity) this,
new String[] {Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
1);
}
///< OpenGL 上下文。
mGLContext = new KFGLContext(null);
///< 渲染视图。
mRenderView = new KFRenderView(this,mGLContext.getContext());
WindowManager windowManager = (WindowManager)this.getSystemService(this.WINDOW_SERVICE);
Rect outRect = new Rect();
windowManager.getDefaultDisplay().getRectSize(outRect);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(outRect.width(), outRect.height());
addContentView(mRenderView,params);
///< 采集配置:摄像头方向、分辨率、帧率。
mCaptureConfig = new KFVideoCaptureConfig();
mCaptureConfig.cameraFacing = LENS_FACING_FRONT;
mCaptureConfig.resolution = new Size(720,1280);
mCaptureConfig.fps = 30;
boolean useCamera2 = false;
if (useCamera2) {
mCapture = new KFVideoCaptureV2();
} else {
mCapture = new KFVideoCaptureV1();
}
mCapture.setup(this,mCaptureConfig,mVideoCaptureListener,mGLContext.getContext());
mCapture.startRunning();
}
private KFVideoCaptureListener mVideoCaptureListener = new KFVideoCaptureListener() {
@Override
///< 相机打开回调。
public void cameraOnOpened(){}
@Override
///< 相机关闭回调。
public void cameraOnClosed() {
}
@Override
///< 相机出错回调。
public void cameraOnError(int error,String errorMsg) {
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
///< 相机数据回调。
public void onFrameAvailable(KFFrame frame) {
mRenderView.render((KFTextureFrame) frame);
}
};
}
上面是 MainActivity
的实现,主要分为以下几个部分:
- 1)创建 OpenGL 上下文。
- 创建上下文
mGLContext
,这样好处是采集与预览可以共享,提高扩展性。
- 创建上下文
- 2)创建采集实例。
- 这里需要注意的是,我们通过开关
useCamera2
选择Camera
或Camera2
。 - 参数配置
mCaptureConfig
,可自定义摄像头方向、帧率、分辨率。
- 这里需要注意的是,我们通过开关
- 3)采集数据回调
onFrameAvailable
,将数据输入给渲染视图进行预览。
更具体细节见上述代码及其注释。