音视频开发路线:

Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门

demo地址:

GitHub - wygsqsj/videoPath: 音视频学习路线demo

录屏功能

录屏需要通过系统构建的Intent再通过startActivityForResult跳转,从回调中拿到MediaProjection,并为他指定Surface,我们的录屏数据就会写入到这个Surface中

1.获取录屏API

private MediaProjection mMediaProjection;    //录屏api
private MediaProjectionManager mediaManager;
//google新版请求回调,用于替换startActivityForResult
private ActivityResultLauncher<Intent> resultLauncher;


private void initMediaProjection() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        mediaManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        //代替startActivityForResult
        resultLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    if (result.getData() != null) {
                        mMediaProjection = mediaManager.getMediaProjection(result.getResultCode(), result.getData());
                        startDisplay();
                    }
                });
    }
}

 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void screenRecording(View view) {
        if (mScreenEncode.getText().toString().contains("开始录屏")) {
            resultLauncher.launch(mediaManager.createScreenCaptureIntent());
        } else {
            screenThread.stopEncode();
            mScreenEncode.setText("开始录屏");
            screenThread = null;
        }
    }

2.将MediaProjection传递给编码线程配置

out264File = new File(demo6Activity.getExternalFilesDir(Environment.DIRECTORY_MOVIES), "cameraOfScreen.h264");
out264File.createNewFile();
fos = new FileOutputStream(out264File);
//构建对应的MeidaFormat
MediaFormat mediaFormat = MediaFormat.createVideoFormat(encodeMine, width, height);
//注意此处要设置成surface类型
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//比特率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
//描述视频格式的帧速率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
//关键帧之间的间隔,此处指定为1秒
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

//构建编码h264MediaCodec
encodeCodec = MediaCodec.createEncoderByType(encodeMine);
encodeCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
inputSurface = encodeCodec.createInputSurface();
initVirtualDisplay();
MediaCodec.BufferInfo encodeBufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
//启动编码器
encodeCodec.start();

MideaFormat的配置项也要相应的改变:

mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void initVirtualDisplay() {
        mMediaProjection.createVirtualDisplay(
                LOG_TAG,  //virtualDisplay 的名字,随意写
                width,
                height,
                demo6Activity.getResources().getDisplayMetrics().densityDpi, // virtualDisplay 的 dpi 值,这里都跟应用保持一致即可
                // 显示的标志位,不同的标志位
                DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                inputSurface, //获取内容的 surface
                null, //回调
                null);  //回调执行的handler
    }

将MediaCodec构建的Surface传递给MediaProjection,这样MediaCodec只需要从OutPutBuffer中取出编码好的264数据即可,不再需要手动为MediaCodec输入数据了

3.取出数据写入文件

while (isEncode) {
    int outputIndex = encodeCodec.dequeueOutputBuffer(encodeBufferInfo, 10000);//返回当前筐的标记
    switch (outputIndex) {
        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            Log.i(LOG_TAG, "输出的format已更改" + encodeCodec.getOutputFormat());
            break;
        case MediaCodec.INFO_TRY_AGAIN_LATER:
            Log.i(LOG_TAG, "超时,没获取到");
            break;
        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
            Log.i(LOG_TAG, "输出缓冲区已更改");
            break;
        default:
            Log.i(LOG_TAG, "获取到surface中的数据,当前解析后的数据长度为:" + encodeBufferInfo.size);
            //获取所有的筐
            ByteBuffer[] outputBuffers = encodeCodec.getOutputBuffers();
            //拿到当前装满火腿肠的筐
            ByteBuffer outputBuffer;
            if (Build.VERSION.SDK_INT >= 21) {
                outputBuffer = encodeCodec.getOutputBuffer(outputIndex);
            } else {
                outputBuffer = outputBuffers[outputIndex];
            }
            //将数据读取到outData中
            byte[] outData = new byte[encodeBufferInfo.size];
            outputBuffer.get(outData);
            //当前是初始化编解码器数据,不是媒体数据,sps、pps等初始化数据
            if (encodeBufferInfo.flags == BUFFER_FLAG_CODEC_CONFIG) {
                Log.i(LOG_TAG, "获取到初始化编解码器数据,长度为:" + encodeBufferInfo.size);
                configByte = new byte[encodeBufferInfo.size];
                configByte = outData;
            } else if (encodeBufferInfo.flags == BUFFER_FLAG_KEY_FRAME) {//当前的数据中包含关键帧数据
                Log.i(LOG_TAG, "获取到I帧数据,长度为:" + encodeBufferInfo.size);
                //将初始化数据和当前的关键帧数据合并后写入到h264文件中
                byte[] keyframe = new byte[encodeBufferInfo.size + configByte.length];
                System.arraycopy(configByte, 0, keyframe, 0, configByte.length);
                //把编码后的视频帧从编码器输出缓冲区中拷贝出来
                System.arraycopy(outData, 0, keyframe, configByte.length, outData.length);
                fos.write(keyframe, 0, keyframe.length);
            } else {
               //写到文件中
                fos.write(outData, 0, outData.length);
            }
            //把筐放回工厂里面
            encodeCodec.releaseOutputBuffer(outputIndex, false);
            break;
    }
}