一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
屏幕录制
在获取到MediaProjection之后,录屏的权限已经获得,接下来就可以进行屏幕录制了,需要创建一virtualDisplay来进行录屏,创建方式如下:
mVirtualDisplay = mMediaProjection.createVirtualDisplay("-display", width, height,
1,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
在创建VirtualDisplay时,注意如下:
a.width、height分别代表录制display对应的宽和高像素大小;
b.surface传值不能为null,为null时,没有屏幕数据产出;
c.当surface为surfaceView.getHolder().getSurface()时,录屏会直接在surfaceView上显示,在加载surfaceview时,需要执行surfaceView.getHolder().setFixedSize(VIDEO_WIDTH, VIDEO_HEIGHT),VIDEO_WIDTH和VIDEO_HEIGHT需要跟createVirtualDisplay时传入的width和height保持一致,否则的话,surfaceview内的视频会有拉伸或位移;
d.当surface = vencoder.createInputSurface()时,获取MediaCodec的surface,这个surface其实就是一个入口,屏幕作为输入源就会进入这个入口,然后交给MediaCodec编码,可以将数据通过网络传输给其他设备显示。
在上述都准备好后,需要MediaCodec登场了,MediaCodec可以访问底层的媒体编解码器,可以对媒体进行编/解码,编码是录屏的过程,解码是显示的过程。
三.MediaCodec编解码
编码是录屏的过程,实时获取屏幕的数据,接下来看一下通过Mediacodec来创建编码器。
a.Encoder
Encoder负责实时获取屏幕数据,将数据储存,供后续通过网络发送屏幕数据。
1.Encoder配置及创建
public static final String MIMETYPE_VIDEO_AVC = "video/avc";
private void startVideoEncoder() {
MediaCodec vencoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
vencoder.configure(format[属性配置], null, null, CONFIGURE_FLAG_ENCODE);
Surface surface = vencoder.createInputSurface();
mVirtualDisplay = mMediaProjection.createVirtualDisplay("-display", width, height,
1,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
vencoder.start();
}
调用MediaCodec的createEncoderByType()来创建Encoder,对应的type为"video/avc",代表屏幕视频数据是H264编码;获取到Encoder对象后,调用Encoder的createInputSurface()来创建surface作为屏幕数据的入口,用来储存后进行发送;配置好Format后,调用start()来启动进行屏幕录制。
2.获取屏幕数据并发送到指定端去渲染显示
MediaCodec.BufferInfo vBufferInfo = new MediaCodec.BufferInfo();
while (isRunning) {
int outputBufferId = vencoder.dequeueOutputBuffer(vBufferInfo, 0);//dequeue有效的Output buffer索引,为了发送传输。
ByteBuffer bb;
if (outputBufferId >= 0) {
if (Build.VERSION.SDK_INT < 21) {
ByteBuffer[] outputBuffers = vencoder.getOutputBuffers();//获取录屏数据存储的Output buffer数组
bb = outputBuffers[outputBufferId];
} else {
bb = vencoder.getOutputBuffer(outputBufferId);
}
}
}
在获取输出缓存时,首先创建一个BufferInfo对象,然后不断循环通过dequeueOutputBuffer(BufferInfo info, long timeoutUs)来请求输出缓存索引outputBufferId,再通过getOutputBuffer()和outputBufferId来获取输出缓存,在获取索引的时候需要传入刚创建的BufferInfo对象,用于存储ByteBuffer的信息,比如:当前是配置帧还是关键帧,使用方式如下:
//读取索引下的有效数据,进行转换后发送到指定端
private void onEncodedAvcFrame(ByteBuffer buffer, MediaCodec.BufferInfo info) {
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
/*
* 特定格式信息等配置数据,不是媒体数据
*/
} else if ((info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
/* delimiter: 00 00 00 01 */
/* I-frame:buf[5]==0x65; SPS:buf[5]==0x67; PPS:buf[5]==0x68; */
}
}
//发送数据
发送数据完成后,释放申请的output buffer,释放方式如下:
vencoder.releaseOutputBuffer(outputBufferId, false[不渲染到surface]);//释放申请的Output buffer