Android MediaProjection和MediaCodec分析2

779 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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