魅力程序猿

  • 首页
  • Java
  • Android
  • APP
    • 扑克计分器
    • Video Wallpaper
  • 联系我
  • 关于我
  • 资助
道子
向阳而生
  1. 首页
  2. Android
  3. 正文

揭秘Android录屏暂停技术

2017年1月10日 13165点热度 0人点赞 7条评论

我是Game Screen Recorder(游戏录屏大师)的主程序员,我来说说录屏使用到技术点,也许不是什么黑科技,但是还是很让人头疼的技术点,不过我已经躺了一次,在这里给大家说一下。

估计大家知道利用 MediaRecorder + MediaProjectionManager + MediaProjection + VirtualDisplay技术点可以实现不能暂停的屏幕录制。那我先说说这个实现的技术:

package com.aoaoyi.screenrecorder.ui;

import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.PermissionChecker;
import android.support.v4.provider.DocumentFile;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.View;
import android.widget.Toast;
import android.widget.ToggleButton;

import com.aoaoyi.screenrecorder.R;

import java.io.IOException;
import java.lang.reflect.Method;

/**
 * Created by yuzhenbei on 2016/3/1.
 */
public class MainActivity1 extends Activity implements ServiceConnection {

    private static final String TAG = MainActivity1.class.getSimpleName();
    private static final int REQUEST_CODE = 1000;
    private int mScreenDensity;
    private MediaProjectionManager mProjectionManager;
    private static final int DISPLAY_WIDTH = 720;
    private static final int DISPLAY_HEIGHT = 1280;
    private MediaProjection mMediaProjection;
    private VirtualDisplay mVirtualDisplay;
    private MediaProjectionCallback mMediaProjectionCallback;
    private ToggleButton mToggleButton;
    private MediaRecorder mMediaRecorder;
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main1);
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        mScreenDensity = metrics.densityDpi;

        mMediaRecorder = new MediaRecorder();

        mProjectionManager = (MediaProjectionManager) getSystemService
                (Context.MEDIA_PROJECTION_SERVICE);

        mToggleButton = (ToggleButton) findViewById(R.id.toggle);
        mToggleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onToggleScreenShare(v);
            }
        });
        //
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode != REQUEST_CODE) {
            Log.e(TAG, "Unknown request code: " + requestCode);
            return;
        }
        if (resultCode != RESULT_OK) {
            Toast.makeText(this,
                    "Screen Cast Permission Denied", Toast.LENGTH_SHORT).show();
            mToggleButton.setChecked(false);
            return;
        }
        mMediaProjectionCallback = new MediaProjectionCallback();
        mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
        mMediaProjection.registerCallback(mMediaProjectionCallback, null);
        mVirtualDisplay = createVirtualDisplay();
        mMediaRecorder.start();
        mMediaRecorder.getMaxAmplitude();
        int _MaxAmplitude = mMediaRecorder.getMaxAmplitude();
        Log.v(TAG, "MaxAmplitude::" + _MaxAmplitude);
        Log.v(TAG, "Permission:1:" + ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO));
        Log.v(TAG, "Permission:2:" + PermissionChecker.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO));
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_DENIED){
            Log.v(TAG, "Permission::" + false);
        }else {
            Log.v(TAG, "Permission::" + true);

        }


    }


    public void onToggleScreenShare(View view) {
        if (((ToggleButton) view).isChecked()) {
            initRecorder();
            shareScreen();
        } else {
            mMediaRecorder.stop();
            mMediaRecorder.reset();
            Log.v(TAG, "Stopping Recording");
            stopScreenSharing();
        }
    }

    private void shareScreen() {
        if (mMediaProjection == null) {
            startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
            return;
        }
        mVirtualDisplay = createVirtualDisplay();
        mMediaRecorder.start();
    }

    private VirtualDisplay createVirtualDisplay() {
        return mMediaProjection.createVirtualDisplay("MainActivity",
                DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                mMediaRecorder.getSurface(), null /*Callbacks*/, null
                /*Handler*/);
    }

    private void initRecorder() {
        try {
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            mMediaRecorder.setOutputFile(Environment
                    .getExternalStoragePublicDirectory(Environment
                            .DIRECTORY_DOWNLOADS) + "/video_11.mp4");
            mMediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            mMediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1000);
            mMediaRecorder.setVideoFrameRate(30);

            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            //pMediaRecorder.setAudioSamplingRate(16000);
            mMediaRecorder.setAudioSamplingRate(44100);
            mMediaRecorder.setAudioEncodingBitRate(96000);
            mMediaRecorder.setAudioChannels(1);

            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            int orientation = ORIENTATIONS.get(rotation + 90);
            mMediaRecorder.setOrientationHint(orientation);
            mMediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private class MediaProjectionCallback extends MediaProjection.Callback {
        @Override
        public void onStop() {
            if (mToggleButton.isChecked()) {
                mToggleButton.setChecked(false);
                mMediaRecorder.stop();
                mMediaRecorder.reset();
                Log.v(TAG, "Recording Stopped");
            }
            mMediaProjection = null;
            stopScreenSharing();
        }
    }

    private void stopScreenSharing() {
        if (mVirtualDisplay == null) {
            return;
        }
        mVirtualDisplay.release();
        //mMediaRecorder.release(); //If used: mMediaRecorder object cannot
        // be reused again
        destroyMediaProjection();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    public void onDestroy() {
        destroyMediaProjection();
        if (mVideoTimelineView != null) {
            mVideoTimelineView.destroy();
        }
        super.onDestroy();
    }

    private void destroyMediaProjection() {
        if (mMediaProjection != null) {
            mMediaProjection.unregisterCallback(mMediaProjectionCallback);
            mMediaProjection.stop();
            mMediaProjection = null;
        }
        Log.i(TAG, "MediaProjection Stopped");
    }

}

主要实现就是上面的代码了,有可能你运行上面的代码无法执行,不过你可以下载我当时测试写的demo,GitHub地址:https://github.com/yuzhenbei/ScreenRecorder-master ,这样你就可以运行一下试试了。这个录制技术实现,我也相信网上很多例子,接下来我们还是谈谈可以暂停的屏幕录制技术实现吧。

我绘制了一张录屏录制可以暂停的技术实现图,咱们先看一下吧:

这样一看,是不是就知道怎么一回事了吧。这个实现的代码也在我当时测试写的demo,GitHub地址:https://github.com/yuzhenbei/ScreenRecorder-master 里有,这里我就不粘贴代码了,代码稍微有点多,也没有什么难点,不过录制的时候有些参数需要根据需求进行调整。

暂停的时候就是利用MediaCodec和MediaMexer进行控制,在循环体内,不对生成的数据进行编码、合成就行了,继续录制的时候,根据标识继续进行编码、合成,停止的时候终止MediaCodec和MediaMexer的循环体。

我觉得还值得注意的:

1、将两种录制实现方式进行融合,实现两种模式来回进行切换,这一块可以需要点时间的;

2、录制放到Service里进行,这个时候就需要对Service进行提高优先级,使其成为前台服务,避免录制过程中Service被回收了;

3、悬浮窗权限问题,我们可以利用WindowManager.LayoutParams.TYPE_TOAST这个实现2016年之前的Rom上可以不要悬浮窗权限就可以调起悬浮窗,但是现在好多Rom进行了修复这个问题,所以合理的提示用户开启悬浮窗权限;

4、录音问题,用Android提供的API是无法实现內录的,这个也是很多用户关注的问题,还有就是不录制声音和录制声音,Audio MediaCodec那个分支不进行实现就可以录制无声的屏幕视频了;

5、录制时的参数调整。

我也发些牢骚,这个项目我提出的,又尽心尽力去做,但是公司领导看不上这个项目,导致项目搁置,我觉得还是可以做的,百度的安卓录屏大师就做的非常好,真是后者居上,还是和资源支持有很大关系。哎……,无尽的惆怅。

对了,那个Demo里面也有视频编辑需要的自定义控件,看看DG Video Editor就知道了哦。

 

标签: Android
最后更新:2017年1月10日

daozi

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

  • http://plaza.rakuten.co.jp/soopierannunzio/diary/201507110001

    Thanks for the good writeup. It if truth be told was a
    enjoyment account it. Look complicated to far introduced agreeable from you!
    By the way, how could we communicate?

    2017年5月27日
    回复
  • hempire hack ios

    Hey, glad that i saw on this in yahoo. Thanks!

    2017年9月26日
    回复
  • easeus partition master 12.5 activation key

    Ha, here from bing, this is what i was looking for.

    2017年9月29日
    回复
  • razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
    回复 easeus partition master 12.5 activation key 取消回复
    搜索
    联系方式

    QQ群:179730949
    QQ群:114559024
    欢迎您加入Android大家庭
    本人QQ:136049925

    赐我一丝安慰
    给我一点鼓励

    COPYRIGHT © 2023 魅力程序猿. ALL RIGHTS RESERVED.

    Theme Kratos Made By Seaton Jiang

    豫ICP备15000477号