MediaProjection与MediaRecorder实现录屏

Posted freeCodeSunny

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MediaProjection与MediaRecorder实现录屏相关的知识,希望对你有一定的参考价值。

       纸上得来终觉浅,绝知此事要躬行,android在5.0提供了MediaProjection来实现录屏,但是一直都没有尝试过,这里尝试了一下该方式进行录屏。

       其实Demo已经写好很久了,但是始终有一个问题,在某些机型上会偏色,因此这里写出来看看是否有人遇到同样的问题,且希望告知解决方案。

录制

       这里界面上有两个按钮,一个控制录制与暂停,另外一个是播放按钮,既然是录制,当然录制完成我们需要看看录制的效果。

       布局界面如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.demo.example.activity.ScreenRecordActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="horizontal">

        <Button
            android:id="@+id/record"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="start"/>

        <Button
            android:id="@+id/play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="play"/>
    </LinearLayout>

    <Chronometer
        android:id="@+id/update"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>

    <TextureView
        android:id="@+id/texture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

两个按钮,一个播放一个停止
Chronometer在界面上改变数字,便于区分视频是否在在录制
TextureView播放界面

       回到Activity,我们这里先初始化一下默认信息

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void init() 
    metrics = this.getResources().getDisplayMetrics();
    width = 720;
    height = 1280;
    //printInfo();
    manager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);

这里宽高就默认为720*1280,也就是视频尺寸
获取了MediaProjectionManager,后面需要他来获得MediaProjection

       上面的准备工作就做完了,这里我们将处理逻辑都放到点击事件上,这里我们点击recoder按钮:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void onRecord() 
    if (isRecording) 
        stopRecord();
     else 
        startRecord();
    
    isRecording = !isRecording;
    record.setText(isRecording ? "stop" : "start");


@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void startRecord() 
    if (mediaProjection != null) // 说明已经请求过权限了
        onStartRecord();
     else 
        Intent captureIntent = manager.createScreenCaptureIntent();
        startActivityForResult(captureIntent, REQUEST_CODE);
    


private void stopRecord() 
    if (recorder != null) 
        recorder.stop();
        recorder.reset();
    

点击后执行onRecord,这里做了区分如果是录制,下一次点击则暂停,第一次会执行startRecord.

       这里我们来主要看看startRecord,这里我们首先判断mediaProjection是否为空,不为空开始录制,为空我们打开了一个Intent,这是因为录屏是需要权限的。打开对应的页面获得权限。那这里第一次会走到onActivityResult,在onActivityResult中我们主要获取了MediaProjection,如果为空则说明失败了,不为空会进入onStartRecord函数。

       onStartRecord主要干了三件事,第一初始化MediaRecorder,第二创建VirtualDisplay

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void onStartRecord() 
    initMediaRecorder();
    createVirtualDisplay();
    recorder.start();
    chronometer.start();

       我们来看看初始化MediaRecorder:


private void initMediaRecorder() 
    if (recorder == null) 
        recorder = new MediaRecorder();
        recorder.setAudiosource(MediaRecorder.AudioSource.MIC);
        recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        recorder.setVideoSize(width, height);
        recorder.setVideoEncodingBitRate(4 * width * height);
        recorder.setVideoFrameRate(30);
     else 
        recorder.reset();
    
    recorder.setOutputFile(getFilePath());
    prepare();


private void prepare() 
    try 
        recorder.prepare();
     catch (IOException e) 
        e.printStackTrace();
        releaseRecorder();
    


private String getFilePath() 
    Date date = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHss");
    String time = dateFormat.format(date);

    String absolutePath = getExternalCacheDir().getAbsolutePath();

    StringBuilder sb = new StringBuilder();
    sb.append(absolutePath).append("/cap/").append(time).append(".3gp");
    fileName = sb.toString();
    File file = new File(fileName);
    File parentFile = file.getParentFile();
    if (!parentFile.exists()) 
        parentFile.mkdir();
    
    return fileName;

上面主要构造了一个MediaRecorder实例,设置了音频,视频,输出格式,编码格式,视频尺寸,码率,帧率,最后设置了视频输出路径

Note:上面设置的参数有先后顺序,一定不能乱序

最后调用了prepare,所有的参数都在prepares之前

       我们来继续看看createVirtualDisplay:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void createVirtualDisplay() 
    virtualDisplay = mediaProjection.createVirtualDisplay("ScreenRecordActivity", width, height, metrics
            .densityDpi, VIRTUAL_DISPLAY_FLAGS, recorder.getSurface(), null, null);

这里我们创建了一个VirtualDisplay,设置了宽,高,密度
设置了recorder.getSurface()将两个联系在一起

       上面的最后一步就是调用了MediaRecorder的start函数,进行录制。

       其他的一些函数都是清场函数,释放占用的资源

播放

       上面已经进行了录制。那这里我们需要进行播放,播放我们采用MediaPlay,MediaPlay大家有用的很多了,这里就大致讲述一下:

private void initMediaPlay() 
    if (mediaPlayer == null) 
        mediaPlayer = new MediaPlayer();
     else 
        mediaPlayer.stop();
        mediaPlayer.reset();
        mediaPlayer.release();
    
    try 
        mediaPlayer.setDataSource(fileName);
        mediaPlayer.setSurface(surface);
        mediaPlayer.prepare();
        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() 
            @Override
            public void onPrepared(MediaPlayer mp) 
                mediaPlayer.start();
            
        );
     catch (IOException e) 
        e.printStackTrace();
    
  • 设置了视频流的路径

  • 这里主要看看setSurface(surface)函数,这里我们设置了一个surface,这里的surface就是之前TextureView构造出来的surface,将视频流与TextureView联系起来。

       那这里的surface是什么构造出来的,我们对TextureView设置了SurfaceTextureListener,在回调中构造surface:

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) 
    surface = new Surface(surfaceTexture);

问题

       目前上面的录制在大部分手机上是没有问题的,但是在Vivo上会偏色,偏色成粉色,我调整了所有参赛,都没有改变改情况。如果有人有解决方案希望告知。

代码

       最后附上源码Code

以上是关于MediaProjection与MediaRecorder实现录屏的主要内容,如果未能解决你的问题,请参考以下文章

mediaprojection : 在某些情况下黑屏

Android录屏功能实现——MediaProjection

API 级别 29 - MediaProjection 始终请求许可

android Q中无法识别MediaProjection服务类型

Android Multimedia框架总结(二十五)MediaProjection实现手机截屏(无须root)

使用 MediaProjection API 从后台服务记录:这种方法是唯一可能的吗?